haematite-cli 0.21.8

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use std::io::{self, BufReader, Read, Write};
use std::net::{Shutdown, TcpListener, TcpStream};
use std::time::Duration;

use super::{LINGER_TIMEOUT, Response, head, linger, write_error};
use crate::mcp::http::fixtures::{Reply, TestResult, refusal};

#[test]
fn an_elapsed_write_deadline_refuses_before_writing_bytes() -> TestResult {
    let listener = TcpListener::bind("127.0.0.1:0")?;
    let caller = TcpStream::connect(listener.local_addr()?)?;
    let (mut socket, _) = listener.accept()?;
    let response = Response {
        status: 200,
        body: b"{}".to_vec(),
        headers: Vec::new(),
    };
    let error = response
        .write(&mut socket, Duration::ZERO)
        .err()
        .ok_or("write should refuse")?;
    assert_eq!(error.status, 408);
    assert_eq!(error.code, "mcp_http_write_timeout");
    drop(caller);
    Ok(())
}

#[test]
fn socket_write_timeouts_keep_their_name_and_io_cause() {
    for kind in [io::ErrorKind::TimedOut, io::ErrorKind::WouldBlock] {
        let error = write_error(&io::Error::from(kind));
        assert_eq!(error.status, 408);
        assert_eq!(error.code, "mcp_http_write_timeout");
        assert!(error.to_string().contains(&format!("io:{kind:?}")));
    }
    let error = write_error(&io::Error::from(io::ErrorKind::BrokenPipe));
    assert_eq!(error.code, "mcp_http_write_failed");
    assert!(error.to_string().contains("BrokenPipe"));
}

#[test]
fn lingering_close_drains_every_byte_after_an_oversized_body() -> TestResult {
    let cap = 4096;
    let listener = TcpListener::bind("127.0.0.1:0")?;
    let mut caller = TcpStream::connect(listener.local_addr()?)?;
    let body = vec![b'x'; cap * 4];
    write!(
        caller,
        "POST /mcp HTTP/1.1\r\nHost: fixture\r\nContent-Length: {}\r\n\r\n",
        body.len()
    )?;
    caller.write_all(&body)?;
    caller.shutdown(Shutdown::Write)?;
    let (socket, _) = listener.accept()?;
    let mut socket = BufReader::new(socket);
    let error = head(&mut socket, LINGER_TIMEOUT, cap)
        .err()
        .ok_or("oversized body should refuse")?;
    assert_eq!(error.status, 413);
    assert_eq!(error.code, "mcp_http_body_refused");
    Response::refused(&error, None)?.write(socket.get_mut(), LINGER_TIMEOUT)?;
    linger(&mut socket)?;
    let mut unread = Vec::new();
    socket.read_to_end(&mut unread)?;
    drop(socket);
    refusal(&Reply::read(caller)?, 413, "mcp_http_body_refused")?;
    assert!(
        unread.is_empty(),
        "lingering close left {} unread request bytes",
        unread.len()
    );
    Ok(())
}