use crate::{Client, Conn};
use std::{
pin::pin,
task::{Context, Poll, Waker},
};
use trillium_http::{Buffer, Error};
use trillium_testing::TestTransport;
fn noop_context() -> Context<'static> {
Context::from_waker(Waker::noop())
}
fn conn_and_server() -> (Conn, TestTransport) {
let (server, conn_side) = TestTransport::new();
let mut conn =
Client::new(trillium_testing::client_config()).build_conn("get", "http://example.test/");
conn.transport = Some(Box::new(conn_side));
(conn, server)
}
const RESPONSE_HEAD: &[u8] = b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
#[test]
fn byte_at_a_time_response_head_still_parses() {
let (mut conn, server) = conn_and_server();
let mut cx = noop_context();
let head_size = {
let mut head = pin!(conn.read_head());
let mut ready = None;
for &byte in RESPONSE_HEAD {
assert!(
ready.is_none(),
"head completed before the full terminator arrived"
);
server.write_all([byte]);
if let Poll::Ready(result) = head.as_mut().poll(&mut cx) {
ready = Some(result);
}
}
ready
.expect("head never completed despite the full head arriving")
.expect("a complete head dripped one byte at a time should parse")
};
assert_eq!(head_size, RESPONSE_HEAD.len());
assert_eq!(&conn.buffer[..], RESPONSE_HEAD);
}
#[test]
fn head_exceeding_head_max_len_errors() {
let (server, conn_side) = TestTransport::new();
let mut context = trillium_http::HttpContext::default();
context.config_mut().set_head_max_len(16);
let mut client = Client::new(trillium_testing::client_config());
client.set_context(context);
let mut conn = client.build_conn("get", "http://example.test/");
conn.transport = Some(Box::new(conn_side));
let mut cx = noop_context();
let mut head = pin!(conn.read_head());
let mut outcome = None;
for _ in 0..16 {
server.write_all(b"A");
if let Poll::Ready(result) = head.as_mut().poll(&mut cx) {
outcome = Some(result);
break;
}
}
assert!(
matches!(outcome, Some(Err(Error::HeadersTooLong))),
"expected HeadersTooLong after exhausting the allowance, got {outcome:?}"
);
}
#[test]
fn eof_before_any_bytes_is_closed() {
let (mut conn, server) = conn_and_server();
server.shutdown(std::net::Shutdown::Write);
let mut cx = noop_context();
let mut head = pin!(conn.read_head());
let outcome = head.as_mut().poll(&mut cx);
assert!(
matches!(outcome, Poll::Ready(Err(Error::Closed))),
"expected Closed on immediate eof, got {outcome:?}"
);
}
#[test]
fn eof_mid_head_is_invalid_head() {
let (mut conn, server) = conn_and_server();
server.write_all(b"HTTP/1.1 200 OK\r\n");
server.shutdown(std::net::Shutdown::Write);
let mut cx = noop_context();
let mut head = pin!(conn.read_head());
let outcome = head.as_mut().poll(&mut cx);
assert!(
matches!(outcome, Poll::Ready(Err(Error::InvalidHead))),
"expected InvalidHead on eof mid-head, got {outcome:?}"
);
}
#[test]
fn prefilled_partial_head_resumes_across_the_boundary() {
let (mut conn, server) = conn_and_server();
let (prefill, remainder) = RESPONSE_HEAD.split_at(RESPONSE_HEAD.len() - 1);
conn.buffer = Buffer::from(prefill.to_vec());
let mut cx = noop_context();
let head_size = {
let mut head = pin!(conn.read_head());
assert!(head.as_mut().poll(&mut cx).is_pending());
server.write_all(remainder);
match head.as_mut().poll(&mut cx) {
Poll::Ready(result) => result.expect("head should parse after the remainder arrives"),
Poll::Pending => panic!("head should complete once the terminator arrives"),
}
};
assert_eq!(head_size, RESPONSE_HEAD.len());
assert_eq!(&conn.buffer[..], RESPONSE_HEAD);
}