use std::{collections::HashMap, future::Future, time::Duration};
use bytes::Bytes;
use http::status::StatusCode;
use http_body_util::Full;
use motore::{
layer::{Identity, Stack},
service::Service,
};
use volo::context::Context;
use super::{
HTTPBIN_GET, HttpBinResponse,
utils::{
AutoBody, AutoBodyLayer, AutoFull, AutoFullLayer, DropBodyLayer, Nothing,
RespBodyToFullLayer,
},
};
use crate::{
ClientBuilder,
body::{Body, BodyConversion},
client::{
CallOpt, Client,
layer::{FailOnStatus, TargetLayer, http_proxy::HttpProxy},
test_helpers::{DebugLayer, MockTransport, RetryOnStatus},
},
context::client::Config,
error::ClientError,
response::Response,
};
fn builder_for_debug()
-> ClientBuilder<Stack<Stack<RetryOnStatus, Identity>, DebugLayer>, Stack<HttpProxy, Identity>> {
Client::builder()
.layer_inner(RetryOnStatus::server_error())
.layer_inner_front(DebugLayer::default())
.layer_outer(HttpProxy::env())
}
#[tokio::test]
async fn client_with_generics() {
let _: Client<Full<Bytes>> = Client::builder().build().unwrap();
let _: Client<Body, Full<Bytes>> = Client::builder()
.layer_outer_front(RespBodyToFullLayer)
.build()
.unwrap();
let _: Client<AutoBody> = Client::builder()
.layer_outer_front(AutoBodyLayer)
.build()
.unwrap();
let _: Client<AutoFull> = Client::builder()
.layer_outer_front(AutoFullLayer)
.build()
.unwrap();
let _: Client<Body, Nothing> = Client::builder()
.layer_outer_front(DropBodyLayer)
.build()
.unwrap();
let _: Client<AutoFull, Nothing> = Client::builder()
.layer_outer_front(AutoFullLayer)
.layer_outer_front(DropBodyLayer)
.build()
.unwrap();
}
#[cfg(feature = "json")]
#[tokio::test]
async fn client_test() {
let client = builder_for_debug().build().unwrap();
{
let resp = client
.get(HTTPBIN_GET)
.send()
.await
.unwrap()
.into_json::<HttpBinResponse>()
.await
.unwrap();
assert!(resp.args.is_empty());
assert_eq!(resp.url, HTTPBIN_GET);
}
{
let resp = client
.get("/get")
.host("httpbin.org")
.send()
.await
.unwrap()
.into_json::<HttpBinResponse>()
.await
.unwrap();
assert!(resp.args.is_empty());
assert_eq!(resp.url, HTTPBIN_GET);
}
}
#[cfg(feature = "json")]
#[tokio::test]
async fn client_target() {
let client = builder_for_debug()
.layer_outer_front(TargetLayer::new_host("httpbin.org"))
.build()
.unwrap();
{
let resp = client
.get(HTTPBIN_GET)
.send()
.await
.unwrap()
.into_json::<HttpBinResponse>()
.await
.unwrap();
assert!(resp.args.is_empty());
assert_eq!(resp.url, HTTPBIN_GET);
}
{
let resp = client
.get("/get")
.send()
.await
.unwrap()
.into_json::<HttpBinResponse>()
.await
.unwrap();
assert!(resp.args.is_empty());
assert_eq!(resp.url, HTTPBIN_GET);
}
}
fn test_data() -> HashMap<String, String> {
HashMap::from([
("key1".to_string(), "val1".to_string()),
("key2".to_string(), "val2".to_string()),
])
}
#[cfg(all(feature = "query", feature = "json"))]
#[tokio::test]
async fn set_query() {
let data = test_data();
let client = builder_for_debug().build().unwrap();
let resp = client
.get("http://httpbin.org/get")
.set_query(&data)
.send()
.await
.unwrap()
.into_json::<HttpBinResponse>()
.await
.unwrap();
assert_eq!(resp.args, data);
}
#[cfg(all(feature = "form", feature = "json"))]
#[tokio::test]
async fn set_form() {
let data = test_data();
let client = builder_for_debug().build().unwrap();
let resp = client
.post("http://httpbin.org/post")
.form(&data)
.send()
.await
.unwrap()
.into_json::<HttpBinResponse>()
.await
.unwrap();
assert_eq!(resp.form, data);
}
#[cfg(feature = "json")]
#[tokio::test]
async fn set_json() {
let data = test_data();
let client = builder_for_debug().build().unwrap();
let resp = client
.post("http://httpbin.org/post")
.json(&data)
.send()
.await
.unwrap()
.into_json::<HttpBinResponse>()
.await
.unwrap();
assert_eq!(resp.json, Some(data));
}
struct GetTimeoutAsSeconds;
impl<Cx, Req> Service<Cx, Req> for GetTimeoutAsSeconds
where
Cx: Context<Config = Config>,
{
type Response = Response;
type Error = ClientError;
fn call(
&self,
cx: &mut Cx,
_: Req,
) -> impl Future<Output = Result<Self::Response, Self::Error>> + Send {
let timeout = cx.rpc_info().config().timeout();
let resp = match timeout {
Some(timeout) => {
let secs = timeout.as_secs();
Response::new(Body::from(format!("{secs}")))
}
None => {
let mut resp = Response::new(Body::empty());
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
resp
}
};
async { Ok(resp) }
}
}
#[tokio::test]
async fn callopt_test() {
let mut builder = builder_for_debug();
builder.set_request_timeout(Duration::from_secs(1));
let client = builder
.layer_outer_front(FailOnStatus::server_error())
.mock(MockTransport::service(GetTimeoutAsSeconds))
.unwrap();
assert_eq!(
client
.get("/")
.send()
.await
.unwrap()
.into_string()
.await
.unwrap(),
"1"
);
assert_eq!(
client
.get("/")
.with_callopt(CallOpt::new().with_timeout(Duration::from_secs(5)))
.send()
.await
.unwrap()
.into_string()
.await
.unwrap(),
"5"
);
}
#[cfg(all(feature = "cookie", feature = "json"))]
#[tokio::test]
async fn cookie_store() {
let client = builder_for_debug()
.layer_inner(crate::client::cookie::CookieLayer::new(Default::default()))
.layer_outer_front(crate::client::layer::TargetLayer::new_host("httpbin.org"))
.build()
.unwrap();
let resp = client
.get("http://httpbin.org/cookies/set?key=value")
.send()
.await
.unwrap();
let cookies = resp
.headers()
.get_all(http::header::SET_COOKIE)
.iter()
.filter_map(|value| {
std::str::from_utf8(value.as_bytes())
.ok()
.and_then(|val| cookie::Cookie::parse(val).map(|c| c.into_owned()).ok())
})
.collect::<Vec<_>>();
assert_eq!(cookies[0].name(), "key");
assert_eq!(cookies[0].value(), "value");
#[derive(serde::Deserialize)]
struct CookieResponse {
#[serde(default)]
cookies: HashMap<String, String>,
}
let resp = client
.get("http://httpbin.org/cookies")
.send()
.await
.unwrap();
let json = resp.into_json::<CookieResponse>().await.unwrap();
assert_eq!(json.cookies["key"], "value");
_ = client
.get("http://httpbin.org/cookies/delete?key")
.send()
.await
.unwrap();
let resp = client
.get("http://httpbin.org/cookies")
.send()
.await
.unwrap();
let json = resp.into_json::<CookieResponse>().await.unwrap();
assert_eq!(json.cookies.len(), 0);
}