net-cat 0.1.0

Minimal hand-rolled HTTP/1.1 client over std::net::TcpStream. Plain HTTP only in v0 (no TLS); used to give web-api-cat's fetch a concrete backend. No external HTTP crate; all parsing and framing are local. No mut beyond the FFI carve-out for TcpStream::read_to_end. Sixth sub-crate of a Servo-replacement webview runtime targeting Tauri.
//! Offline tests for the public surface (no network).

use net_cat::{Error, Headers, Method, Request, Url};

fn fail(msg: &'static str) -> Error {
    Error::InvalidUrl {
        source: msg.to_owned(),
    }
}

#[test]
fn parse_basic_url() -> Result<(), Error> {
    let url = Url::parse("http://example.com/")?;
    (url.scheme() == "http" && url.host() == "example.com" && url.port() == 80 && url.path() == "/")
        .then_some(())
        .ok_or_else(|| fail("basic url shape"))
}

#[test]
fn parse_url_with_port_and_query() -> Result<(), Error> {
    let url = Url::parse("http://example.com:8080/api/v1?x=1&y=2")?;
    (url.port() == 8080
        && url.path() == "/api/v1"
        && url.query() == Some("x=1&y=2")
        && url.request_target() == "/api/v1?x=1&y=2")
        .then_some(())
        .ok_or_else(|| fail("port + query shape"))
}

#[test]
fn parse_url_missing_path_implicit_root() -> Result<(), Error> {
    let url = Url::parse("http://example.com")?;
    (url.path() == "/")
        .then_some(())
        .ok_or_else(|| fail("implicit root path"))
}

#[test]
fn parse_url_rejects_garbage() -> Result<(), Error> {
    match Url::parse("not a url") {
        Err(Error::InvalidUrl { .. }) => Ok(()),
        _other => Err(fail("expected InvalidUrl")),
    }
}

#[test]
fn https_url_parses_but_marked_unsupported_in_fetch_path() -> Result<(), Error> {
    // `Url::parse` accepts the scheme; only `fetch` rejects it.  Verify
    // the URL itself can be constructed so callers can introspect.
    let url = Url::parse("https://example.com/")?;
    (url.scheme() == "https" && url.port() == 443)
        .then_some(())
        .ok_or_else(|| fail("https url should be parseable"))
}

#[test]
fn request_builder_round_trip() -> Result<(), Error> {
    let url = Url::parse("http://example.com/")?;
    let request = Request::new(Method::Post, url)
        .with_header("X-Test", "yes")
        .with_body(b"hello".to_vec());
    (request.method() == Method::Post
        && request.headers().get("x-test") == Some("yes")
        && request.body() == b"hello")
        .then_some(())
        .ok_or_else(|| fail("request builder"))
}

#[test]
fn headers_case_insensitive_lookup() -> Result<(), Error> {
    let headers = Headers::new()
        .with("Content-Type", "text/plain")
        .with("X-Custom", "v");
    (headers.get("content-type") == Some("text/plain") && headers.get("X-CUSTOM") == Some("v"))
        .then_some(())
        .ok_or_else(|| fail("case-insensitive header"))
}

#[test]
fn method_wire_strings() -> Result<(), Error> {
    let pairs = [
        (Method::Get, "GET"),
        (Method::Post, "POST"),
        (Method::Put, "PUT"),
        (Method::Delete, "DELETE"),
        (Method::Head, "HEAD"),
        (Method::Options, "OPTIONS"),
        (Method::Patch, "PATCH"),
    ];
    pairs
        .iter()
        .all(|(m, s)| m.as_str() == *s)
        .then_some(())
        .ok_or_else(|| fail("method wire strings"))
}