use barehttp::config::{Config, ConfigBuilder};
use barehttp::{
Authority, ClientRequestBuilder, DecompressError, Error, Headers, Host, HttpClient, IntoStringError, Method,
OsBlockingSocket, OsDnsResolver, ParseError, ParseMethodError, Response, Uri, Version,
};
use core::error::Error as StdError;
use core::hash::{Hash, Hasher};
use core::str::FromStr;
fn assert_copy<T: Copy>() {}
fn assert_hash<T: Hash>() {}
fn assert_eq_<T: Eq + PartialEq>() {}
fn assert_default<T: Default>() {}
fn assert_send_sync<T: Send + Sync>() {}
fn hash_value<T: Hash>(value: &T) -> u64 {
struct CountingHasher(u64);
impl Hasher for CountingHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(
&mut self,
bytes: &[u8],
) {
for b in bytes {
self.0 = self.0.wrapping_mul(31).wrapping_add(u64::from(*b));
}
}
}
let mut h = CountingHasher(0);
value.hash(&mut h);
h.finish()
}
#[test]
fn uri_authority_host_are_copy_and_hash() {
assert_copy::<Uri<'static>>();
assert_copy::<Authority<'static>>();
assert_copy::<Host<'static>>();
assert_hash::<Uri<'static>>();
assert_hash::<Authority<'static>>();
assert_hash::<Host<'static>>();
assert_eq_::<Uri<'static>>();
let uri = Uri::parse("http://example.com/path?q=1").expect("uri");
assert_eq!(uri.query(), Some("q=1"));
let _ = hash_value(&uri);
let auth = uri.authority().copied().expect("authority");
let _ = hash_value(&auth);
let _ = hash_value(&auth.host());
}
#[test]
fn version_and_method_defaults() {
assert_default::<Version>();
assert_default::<Method>();
assert_eq!(Version::default(), Version::HTTP_11);
assert_eq!(Method::default(), Method::Get);
}
#[test]
fn parse_method_error_is_exhaustive_outside_crate() {
fn match_exhaustive(err: ParseMethodError) -> &'static str {
match err {
ParseMethodError::InvalidToken => "invalid",
}
}
assert_eq!(match_exhaustive(ParseMethodError::InvalidToken), "invalid");
assert!(matches!(Method::from_str("NOPE"), Ok(Method::Extension(_))));
assert_eq!(Method::from_str("A/B"), Err(ParseMethodError::InvalidToken));
}
#[test]
fn config_is_hash_and_builder_type_exists() {
assert_hash::<Config>();
assert_eq_::<Config>();
let a = Config::default();
let b = Config::builder().build();
assert_eq!(hash_value(&a), hash_value(&b));
let _: ConfigBuilder = Config::builder();
}
#[test]
fn headers_insert_set_accept_str_without_string() {
let mut headers = Headers::new();
let name: &str = "X-Trace";
let value: &str = "abc";
headers.insert(name, value);
headers.set("content-type", "text/plain");
assert_eq!(headers.get("x-trace"), Some("abc"));
assert_eq!(headers.get("Content-Type"), Some("text/plain"));
}
#[test]
fn headers_owned_into_iter_yields_strings() {
let mut headers = Headers::new();
headers.insert("Host", "example.com");
headers.insert("X-A", "1");
let owned: Vec<(String, String)> = headers.into_iter().collect();
assert_eq!(owned.len(), 2);
assert_eq!(owned[0], ("Host".into(), "example.com".into()));
assert_eq!(owned[1], ("X-A".into(), "1".into()));
}
#[allow(dead_code)]
fn api_request_accepts_option_slice(client: &HttpClient<OsBlockingSocket, OsDnsResolver>) {
let headers = Headers::new();
let _ = client.request(Method::Get, "http://example.com/", &headers, None::<&[u8]>);
let _ = client.request(Method::Post, "http://example.com/", &headers, Some(&b"hi"[..]));
let _ = client.request_with_config(
client.config(),
Method::Get,
"http://example.com/",
&headers,
None::<&[u8]>,
);
let owned = vec![1u8, 2];
let _ = client.request(Method::Put, "http://example.com/", &headers, Some(owned.as_slice()));
}
#[test]
fn request_body_option_slice_signature_compiles() {
let _ = api_request_accepts_option_slice as fn(&HttpClient<OsBlockingSocket, OsDnsResolver>);
}
#[test]
fn primary_type_names_and_undeprecated_aliases() {
let _: HttpClient<OsBlockingSocket, OsDnsResolver> = HttpClient::new();
let _: ClientRequestBuilder<OsBlockingSocket, OsDnsResolver> = HttpClient::new().get("http://example.com/");
let _: barehttp::Agent = barehttp::agent();
let _: barehttp::RequestBuilder = barehttp::get("http://example.com/");
}
#[test]
fn body_exceeds_limit_lifts_out_of_parse_error() {
let err: Error = ParseError::BodyExceedsLimit(64).into();
assert!(matches!(err, Error::BodyExceedsLimit(64)));
assert!(!matches!(err, Error::Parse(_)));
}
#[test]
fn decompression_parse_error_exposes_source() {
let err = ParseError::Decompression(DecompressError::InvalidInput);
let source = StdError::source(&err).expect("Decompression must expose source()");
assert!(source.is::<DecompressError>());
assert_eq!(
source.downcast_ref::<DecompressError>(),
Some(&DecompressError::InvalidInput)
);
let limit = ParseError::Decompression(DecompressError::LimitExceeded);
assert!(StdError::source(&limit).is_some());
}
#[test]
fn response_to_text_and_into_string_error_shapes() {
let ok = Response::parse(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok").expect("parse");
assert_eq!(ok.status_code(), 200);
assert_eq!(ok.body(), b"ok");
let text: Result<&str, core::str::Utf8Error> = ok.to_text();
assert_eq!(text.expect("utf8"), "ok");
let bad = Response::parse(b"HTTP/1.1 201 Created\r\nContent-Length: 1\r\n\r\n\xff").expect("parse");
let utf8_err = bad.to_text().expect_err("invalid utf8");
let _ = utf8_err;
let bad = Response::parse(b"HTTP/1.1 201 Created\r\nContent-Length: 1\r\n\r\n\xff").expect("parse");
let into_err: IntoStringError = bad.into_string().expect_err("into_string");
assert_eq!(into_err.response().status_code(), 201);
assert_eq!(into_err.response().body(), [0xff]);
let recovered = into_err.into_response();
assert_eq!(recovered.status_code(), 201);
}
#[test]
fn primary_accessors_status_code_and_body() {
let r = Response::parse(b"HTTP/1.1 204 No Content\r\nContent-Length: 0\r\n\r\n").expect("parse");
assert_eq!(r.status_code(), 204);
assert_eq!(r.body(), b"");
#[allow(deprecated)]
{
assert_eq!(r.status(), 204);
assert_eq!(r.as_bytes(), b"");
}
}
#[test]
fn into_bytes_returns_vec_u8() {
let r = Response::parse(b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nxyz").expect("parse");
let body: Vec<u8> = r.into_bytes();
assert_eq!(body, b"xyz");
}
#[test]
fn response_is_hash() {
assert_hash::<Response>();
let a = Response::parse(b"HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\nx").expect("parse");
let b = Response::parse(b"HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\nx").expect("parse");
assert_eq!(hash_value(&a), hash_value(&b));
}
#[test]
fn http_status_error_recovers_boxed_response() {
let resp = Response::parse(b"HTTP/1.1 503 Service Unavailable\r\nContent-Length: 4\r\n\r\ndown").expect("parse");
let err = Error::HttpStatus(503, Box::new(resp));
match err {
Error::HttpStatus(code, boxed) => {
assert_eq!(code, 503);
assert_eq!(boxed.status_code(), 503);
assert_eq!(boxed.body(), b"down");
},
other => panic!("unexpected: {other:?}"),
}
}
#[cfg(feature = "cookie-jar")]
mod cookie_jar_api {
use super::{assert_eq_, assert_hash, hash_value};
use barehttp::cookie_jar::{CookieJar, CookieStore, StoredCookie};
use barehttp::{ParseError, Uri};
#[test]
fn stored_cookie_partial_eq_eq_hash() {
assert_eq_::<StoredCookie>();
assert_hash::<StoredCookie>();
let store = CookieStore::new();
store
.store_response_cookies("http://example.com/", ["id=1"])
.expect("uri");
let mut iter = store.iter();
let first = iter.next().expect("cookie").clone();
drop(iter);
let _ = hash_value(&first);
assert_eq!(first.name(), "id");
assert_eq!(first.value(), "1");
}
#[test]
fn store_response_cookies_accepts_str_slices() {
let store = CookieStore::new();
let headers: &[&str] = &["session=abc; Path=/", "theme=dark"];
store
.store_response_cookies("http://example.com/", headers)
.expect("uri");
assert_eq!(
store.request_cookie_header("http://example.com/"),
"session=abc; theme=dark"
);
}
#[test]
fn store_response_cookies_invalid_uri_is_err() {
let store = CookieStore::new();
assert_eq!(
store.store_response_cookies("://bad", ["a=1"]),
Err(ParseError::InvalidUri)
);
}
#[test]
fn cookie_jar_alias_is_cookie_store() {
let jar: CookieJar = CookieStore::new();
jar
.store_response_cookies("http://example.com/", ["x=1"])
.expect("uri");
assert_eq!(jar.request_cookie_header("http://example.com/"), "x=1");
}
#[test]
fn cookie_store_accessor_returns_store_ref_not_arc() {
use barehttp::HttpClient;
let client = HttpClient::new();
let store: &CookieStore = client.cookie_store();
assert!(store.iter().next().is_none());
}
#[test]
fn request_cookie_header_one_arg_https_enables_secure() {
let store = CookieStore::new();
store
.store_response_cookies("https://example.com/", ["token=secret; Secure"])
.expect("uri");
let https = store.request_cookie_header("https://example.com/");
assert_eq!(https, "token=secret");
let http = store.request_cookie_header("http://example.com/");
assert_eq!(http, "");
let uri = Uri::parse("https://example.com/").expect("uri");
let _ = store.request_cookie_header("https://example.com/");
let _ = uri; }
#[test]
fn same_site_is_public_and_stored() {
use barehttp::cookie_jar::SameSite;
let store = CookieStore::new();
store
.store_response_cookies("https://example.com/", ["x=1; SameSite=Strict; Secure"])
.expect("uri");
let c = store.iter().next().expect("cookie");
assert_eq!(c.same_site(), SameSite::Strict);
assert!(!c.http_only());
}
}
#[test]
fn agent_still_send_sync() {
assert_send_sync::<barehttp::Agent>();
assert_send_sync::<HttpClient<OsBlockingSocket, OsDnsResolver>>();
assert_send_sync::<Error>();
assert_send_sync::<Response>();
assert_send_sync::<Config>();
}