servers/http/
test_helpers.rsuse std::convert::TryFrom;
use std::net::{SocketAddr, TcpListener};
use std::str::FromStr;
use axum::body::HttpBody;
use axum::BoxError;
use bytes::Bytes;
use common_telemetry::info;
use http::header::{HeaderName, HeaderValue};
use http::{Request, StatusCode};
use hyper::service::Service;
use hyper::{Body, Server};
use tower::make::Shared;
pub struct TestClient {
client: reqwest::Client,
addr: SocketAddr,
}
impl TestClient {
pub fn new<S, ResBody>(svc: S) -> Self
where
S: Service<Request<Body>, Response = http::Response<ResBody>> + Clone + Send + 'static,
ResBody: HttpBody + Send + 'static,
ResBody::Data: Send,
ResBody::Error: Into<BoxError>,
S::Future: Send,
S::Error: Into<BoxError>,
{
let listener = TcpListener::bind("127.0.0.1:0").expect("Could not bind ephemeral socket");
let addr = listener.local_addr().unwrap();
info!("Listening on {}", addr);
tokio::spawn(async move {
let server = Server::from_tcp(listener).unwrap().serve(Shared::new(svc));
server.await.expect("server error");
});
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();
TestClient { client, addr }
}
pub fn base_url(&self) -> String {
format!("http://{}", self.addr)
}
pub fn get(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.get(format!("http://{}{}", self.addr, url)),
}
}
pub fn head(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.head(format!("http://{}{}", self.addr, url)),
}
}
pub fn post(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.post(format!("http://{}{}", self.addr, url)),
}
}
pub fn put(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.put(format!("http://{}{}", self.addr, url)),
}
}
pub fn patch(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.patch(format!("http://{}{}", self.addr, url)),
}
}
pub fn delete(&self, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self.client.delete(format!("http://{}{}", self.addr, url)),
}
}
}
pub struct RequestBuilder {
builder: reqwest::RequestBuilder,
}
impl RequestBuilder {
pub async fn send(self) -> TestResponse {
TestResponse {
response: self.builder.send().await.unwrap(),
}
}
pub fn body(mut self, body: impl Into<reqwest::Body>) -> Self {
self.builder = self.builder.body(body);
self
}
pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
self.builder = self.builder.form(&form);
self
}
pub fn json<T>(mut self, json: &T) -> Self
where
T: serde::Serialize,
{
self.builder = self.builder.json(json);
self
}
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
let key: HeaderName = key.try_into().map_err(Into::into).unwrap();
let key = reqwest::header::HeaderName::from_bytes(key.as_ref()).unwrap();
let value: HeaderValue = value.try_into().map_err(Into::into).unwrap();
let value = reqwest::header::HeaderValue::from_bytes(value.as_bytes()).unwrap();
self.builder = self.builder.header(key, value);
self
}
pub fn multipart(mut self, form: reqwest::multipart::Form) -> Self {
self.builder = self.builder.multipart(form);
self
}
}
#[derive(Debug)]
pub struct TestResponse {
response: reqwest::Response,
}
impl TestResponse {
pub async fn text(self) -> String {
self.response.text().await.unwrap()
}
pub async fn bytes(self) -> Bytes {
self.response.bytes().await.unwrap()
}
pub async fn json<T>(self) -> T
where
T: serde::de::DeserializeOwned,
{
self.response.json().await.unwrap()
}
pub fn status(&self) -> StatusCode {
StatusCode::from_u16(self.response.status().as_u16()).unwrap()
}
pub fn headers(&self) -> http::HeaderMap {
let mut headers = http::HeaderMap::new();
for (key, value) in self.response.headers() {
let key = HeaderName::from_str(key.as_str()).unwrap();
let value = HeaderValue::from_bytes(value.as_bytes()).unwrap();
headers.insert(key, value);
}
headers
}
pub async fn chunk(&mut self) -> Option<Bytes> {
self.response.chunk().await.unwrap()
}
pub async fn chunk_text(&mut self) -> Option<String> {
let chunk = self.chunk().await?;
Some(String::from_utf8(chunk.to_vec()).unwrap())
}
pub fn into_inner(self) -> reqwest::Response {
self.response
}
}
impl AsRef<reqwest::Response> for TestResponse {
fn as_ref(&self) -> &reqwest::Response {
&self.response
}
}