use flate2::Compression;
use flate2::write::GzEncoder;
use futures_lite::future::block_on;
use httpmock::prelude::*;
use serde_json::json;
use std::io::Write;
use ugi::{Client, CompressionMode, Version};
fn run<T>(value: T) -> T::Output
where
T: std::future::IntoFuture,
{
block_on(async move { value.await })
}
#[test]
fn client_defaults_and_request_overrides_compose_cleanly() {
let server = MockServer::start();
let profile = server.mock(|when, then| {
when.method(GET)
.path("/profile")
.header("authorization", "Bearer client-token")
.header("x-client", "request")
.header("cookie", "theme=light");
then.status(200)
.header("content-type", "application/json")
.body(json!({ "ok": true }).to_string());
});
let client = Client::builder()
.base_url(server.base_url())
.unwrap()
.bearer_auth("client-token")
.unwrap()
.header("x-client", "base")
.unwrap()
.cookie("theme", "light")
.build()
.unwrap();
let response = run(client
.get("/profile")
.header("x-client", "request")
.unwrap())
.unwrap();
assert_eq!(response.version(), Version::Http11);
assert_eq!(
block_on(response.json::<serde_json::Value>()).unwrap(),
json!({ "ok": true })
);
profile.assert();
}
#[test]
fn request_level_manual_compression_overrides_client_auto() {
let server = MockServer::start();
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(b"raw-compressed").unwrap();
let compressed = encoder.finish().unwrap();
let download = server.mock(|when, then| {
when.method(GET)
.path("/download")
.header("accept-encoding", "gzip");
then.status(200)
.header("content-encoding", "gzip")
.body(compressed.clone());
});
let client = Client::builder().build().unwrap();
let response = run(client
.get(server.url("/download"))
.compression_mode(CompressionMode::Manual)
.header("accept-encoding", "gzip")
.unwrap())
.unwrap();
assert_eq!(response.headers().get("content-encoding"), Some("gzip"));
assert_eq!(
block_on(response.bytes()).unwrap().as_ref(),
compressed.as_slice()
);
download.assert();
}
#[test]
fn metrics_are_available_after_base_url_request() {
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method(GET).path("/metrics");
then.status(200).body("ok");
});
let client = Client::builder()
.base_url(server.base_url())
.unwrap()
.build()
.unwrap();
let response = run(client.get("/metrics")).unwrap();
let metrics = response.metrics();
assert_eq!(metrics.protocol(), Some(Version::Http11));
assert!(metrics.ttfb().is_some());
assert!(metrics.request_write_duration().is_some());
mock.assert();
}
#[test]
fn auto_compression_decodes_gzip_response_body() {
let server = MockServer::start();
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(b"decoded-body").unwrap();
let compressed = encoder.finish().unwrap();
let mock = server.mock(|when, then| {
when.method(GET)
.path("/gzip")
.matches(|req: &HttpMockRequest| {
let headers = match &req.headers {
Some(h) => h,
None => return false,
};
let accept = headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case("accept-encoding"))
.map(|(_, v)| v.to_ascii_lowercase());
let accept = match accept {
Some(v) => v,
None => return false,
};
if !accept.contains("gzip") || !accept.contains("deflate") {
return false;
}
if cfg!(feature = "brotli") && !accept.contains("br") {
return false;
}
if cfg!(feature = "zstd") && !accept.contains("zstd") {
return false;
}
true
});
then.status(200)
.header("content-encoding", "gzip")
.body(compressed.clone());
});
let response = run(ugi::get(server.url("/gzip"))).unwrap();
assert_eq!(block_on(response.text()).unwrap(), "decoded-body");
mock.assert();
}
#[test]
fn same_origin_redirect_preserves_client_authorization() {
let server = MockServer::start();
let start = server.mock(|when, then| {
when.method(GET)
.path("/start")
.header("authorization", "Bearer token");
then.status(302).header("location", "/final");
});
let final_mock = server.mock(|when, then| {
when.method(GET)
.path("/final")
.header("authorization", "Bearer token");
then.status(200).body("ok");
});
let client = Client::builder()
.bearer_auth("token")
.unwrap()
.build()
.unwrap();
let response = run(client.get(server.url("/start"))).unwrap();
assert_eq!(block_on(response.text()).unwrap(), "ok");
start.assert();
final_mock.assert();
}
#[test]
fn cookie_store_and_base_url_work_together() {
let server = MockServer::start();
let login = server.mock(|when, then| {
when.method(GET).path("/login");
then.status(200)
.header("set-cookie", "sid=matrix; Path=/")
.body("logged-in");
});
let me = server.mock(|when, then| {
when.method(GET).path("/me").header("cookie", "sid=matrix");
then.status(200).body("me");
});
let client = Client::builder()
.base_url(server.base_url())
.unwrap()
.cookie_store()
.build()
.unwrap();
let first = run(client.get("/login")).unwrap();
assert_eq!(block_on(first.text()).unwrap(), "logged-in");
let second = run(client.get("/me")).unwrap();
assert_eq!(block_on(second.text()).unwrap(), "me");
login.assert();
me.assert();
}