sark-http 0.29.5

HTTP wire types, semantics, codecs, and ownership invariants for Sark
pub mod common;

use common::response::{coding, headers};
use dope_test::alloc::Tracker;
use sark_http::{
    field::Value,
    h1::{
        codec::decode::response::Progress,
        head::{Limit, response::State},
    },
    payload::response::{Method, Response, status::Code},
};

#[global_allocator]
static ALLOCATOR: Tracker = Tracker::new();

#[test]
fn connect_transition_stays_typed() {
    use sark_http::h1::{
        codec::decode::response::{Decoder, Error},
        framing,
    };

    let wire = common::head::CONNECT_RESPONSES[1];
    assert!(matches!(
        Decoder::new(Method::Connect).scan(wire, wire.len()),
        Err(Error::Framing(
            framing::Error::SuccessfulConnectIsTransition
        ))
    ));
}

#[test]
fn body_allocation_failures() {
    use sark_http::h1::codec::decode::response::{Decoder, Error};

    let decoder = Decoder::new(Method::Other);
    assert!(matches!(
        Tracker::fail_after(0, || decoder.finish(headers::EOF_BODY)),
        Err(Error::Allocation(o3::alloc::Exhausted))
    ));
}

#[test]
fn header_allocation_failures() {
    use sark_http::{
        h1::codec::decode::fields::{self, Sink as _},
        payload::response::headers::List,
    };

    let mut list = List::new();
    assert!(matches!(
        Tracker::fail_after(1, || list.push(b"content-type", headers::OPAQUE)),
        Err(fields::Error::Allocation(o3::alloc::Error::Exhausted))
    ));
    assert!(list.is_empty());
}

#[test]
fn constructors_keep_parts() {
    let resp = Response::new(Code::OK);

    assert_eq!(resp.status(), Code::OK);

    let resp = Response::new(Code::CREATED).with_body(b"Created resource".to_vec());

    assert_eq!(resp.status(), Code::CREATED);
    assert_eq!(resp.body().as_slice(), b"Created resource");
}

#[test]
fn trailers_stay_separate() {
    use sark_http::h1::codec::decode::response::Decoder;

    let wire = headers::TRAILERS;
    {
        let response =
            match (Decoder::new(Method::Other).decode(wire)).expect("response must decode") {
                Progress::Complete(value) => Some(value),
                Progress::More => None,
            }
            .expect("test input must be complete");
        assert_eq!(
            response.headers().get("x-head").map(Value::as_bytes),
            Some(b"head".as_slice()),
        );
        assert!(!response.headers().contains_key("x-tail"));
        assert_eq!(
            response.trailers().get("x-tail").map(Value::as_bytes),
            Some(headers::OPAQUE),
        );
    };
}

#[test]
fn header_repeats_preserved() {
    use sark_http::h1::codec::decode::response::Decoder;

    let wire = headers::REPEATED;
    {
        let response =
            match (Decoder::new(Method::Other).decode(wire)).expect("response must decode") {
                Progress::Complete(value) => Some(value),
                Progress::More => None,
            }
            .expect("test input must be complete");
        let cookies: Vec<_> = response
            .headers()
            .iter()
            .filter(|entry| entry.0.as_str() == "set-cookie")
            .map(|entry| entry.1.as_bytes())
            .collect();

        assert_eq!(cookies, [b"a=1".as_slice(), headers::OPAQUE]);
        assert_eq!(
            response.headers().get("Set-Cookie").map(Value::as_bytes),
            Some(b"a=1".as_slice())
        );
        assert_eq!(response.headers().len(), 3);
        let names: Vec<_> = response
            .headers()
            .iter()
            .map(|(name, _)| name.as_str())
            .collect();
        assert_eq!(names, ["set-cookie", "set-cookie", "content-length"]);
    };
}

#[test]
fn status_code() {
    use sark_http::h1::codec::decode::response::Decoder;

    let wire = headers::STATUS;
    {
        let response =
            match (Decoder::new(Method::Other).decode(wire)).expect("response must decode") {
                Progress::Complete(value) => Some(value),
                Progress::More => None,
            }
            .expect("test input must be complete");
        assert_eq!(response.status().as_u16(), 599);
    };
}

#[test]
fn decoded_headers_own_data() {
    use sark_http::h1::codec::decode::response::Decoder;

    let response = {
        let wire = headers::TRAILERS.to_vec();
        Decoder::new(Method::Other)
            .finish(&wire)
            .expect("complete owned response")
    };
    assert_eq!(
        response.headers().get("x-head").map(Value::as_bytes),
        Some(b"head".as_slice())
    );
    assert_eq!(
        response.trailers().get("x-tail").map(Value::as_bytes),
        Some(headers::OPAQUE)
    );
}

#[test]
fn coding_fragments() {
    for (wire, expected) in coding::CASES {
        State::with(
            Vec::new(),
            Method::Other,
            Limit::RESPONSE,
            |mut source, mut progress| {
                for end in 0..wire.len() {
                    let (next, head) = progress.scan(&source).expect("partial head");
                    progress = next;
                    assert!(head.is_none());
                    source.try_extend(&wire[end..=end]).expect("append byte");
                }
                let (_, head) = progress.scan(&source).expect("response head");
                let admitted = head
                    .expect("complete head")
                    .admit()
                    .expect("response framing");
                assert_eq!(admitted.coding(), expected);
            },
        );
    }
}