use ::anyhow::anyhow;
use ::anyhow::Context;
use ::anyhow::Result;
use ::auto_future::AutoFuture;
use ::axum::http::HeaderValue;
use ::cookie::Cookie;
use ::cookie::CookieJar;
use ::hyper::body::to_bytes;
use ::hyper::body::Body;
use ::hyper::body::Bytes;
use ::hyper::header;
use ::hyper::header::HeaderName;
use ::hyper::http::header::SET_COOKIE;
use ::hyper::http::Request;
use ::hyper::Client;
use ::serde::Serialize;
use ::serde_json::to_vec as json_to_vec;
use ::std::convert::AsRef;
use ::std::fmt::Debug;
use ::std::fmt::Display;
use ::std::future::IntoFuture;
use ::std::sync::Arc;
use ::std::sync::Mutex;
use crate::InnerTestServer;
use crate::TestResponse;
mod test_request_config;
pub(crate) use self::test_request_config::*;
const JSON_CONTENT_TYPE: &'static str = &"application/json";
const TEXT_CONTENT_TYPE: &'static str = &"text/plain";
#[derive(Debug)]
#[must_use = "futures do nothing unless polled"]
pub struct TestRequest {
config: TestRequestConfig,
inner_test_server: Arc<Mutex<InnerTestServer>>,
body: Option<Body>,
headers: Vec<(HeaderName, HeaderValue)>,
cookies: CookieJar,
is_expecting_failure: bool,
}
impl TestRequest {
pub(crate) fn new(
inner_test_server: Arc<Mutex<InnerTestServer>>,
config: TestRequestConfig,
) -> Result<Self> {
let server_locked = inner_test_server.as_ref().lock().map_err(|err| {
anyhow!(
"Failed to lock InternalTestServer for {} {}, received {:?}",
config.method,
config.path,
err
)
})?;
let cookies = server_locked.cookies().clone();
::std::mem::drop(server_locked);
Ok(Self {
config,
inner_test_server,
body: None,
headers: vec![],
cookies,
is_expecting_failure: false,
})
}
pub fn do_save_cookies(mut self) -> Self {
self.config.is_saving_cookies = true;
self
}
pub fn do_not_save_cookies(mut self) -> Self {
self.config.is_saving_cookies = false;
self
}
pub fn clear_cookies(mut self) -> Self {
self.cookies = CookieJar::new();
self
}
pub fn add_cookie<'c>(mut self, cookie: Cookie<'c>) -> Self {
self.cookies.add(cookie.into_owned());
self
}
pub fn expect_failure(mut self) -> Self {
self.is_expecting_failure = true;
self
}
pub fn expect_success(mut self) -> Self {
self.is_expecting_failure = false;
self
}
pub fn json<J>(mut self, body: &J) -> Self
where
J: ?Sized + Serialize,
{
let body_bytes = json_to_vec(body).expect("It should serialize the content into JSON");
let body: Body = body_bytes.into();
self.body = Some(body);
if self.config.content_type == None {
self.config.content_type = Some(JSON_CONTENT_TYPE.to_string());
}
self
}
pub fn text<T>(mut self, raw_text: T) -> Self
where
T: Display,
{
let body_text = format!("{}", raw_text);
let body_bytes = Bytes::from(body_text.into_bytes());
if self.config.content_type == None {
self.config.content_type = Some(TEXT_CONTENT_TYPE.to_string());
}
self.bytes(body_bytes)
}
pub fn bytes(mut self, body_bytes: Bytes) -> Self {
let body: Body = body_bytes.into();
self.body = Some(body);
self
}
pub fn content_type(mut self, content_type: &str) -> Self {
self.config.content_type = Some(content_type.to_string());
self
}
async fn send_or_panic(self) -> TestResponse {
self.send().await.expect("Sending request failed")
}
async fn send(mut self) -> Result<TestResponse> {
let full_request_path = self.config.full_request_path;
let method = self.config.method;
let path = self.config.path;
let save_cookies = self.config.is_saving_cookies;
let body = self.body.unwrap_or(Body::empty());
let mut request_builder = Request::builder().uri(&full_request_path).method(method);
let mut headers = self.headers;
if let Some(content_type) = self.config.content_type {
let header = build_content_type_header(content_type)?;
headers.push(header);
}
for cookie in self.cookies.iter() {
let cookie_raw = cookie.to_string();
let header_value = HeaderValue::from_str(&cookie_raw)?;
headers.push((header::COOKIE, header_value));
}
for (header_name, header_value) in headers {
request_builder = request_builder.header(header_name, header_value);
}
let request = request_builder.body(body).with_context(|| {
format!(
"Expect valid hyper Request to be built on request to {}",
path
)
})?;
let hyper_response = Client::new()
.request(request)
.await
.with_context(|| format!("Expect Hyper Response to succeed on request to {}", path))?;
let (parts, response_body) = hyper_response.into_parts();
let response_bytes = to_bytes(response_body).await?;
if save_cookies {
let cookie_headers = parts.headers.get_all(SET_COOKIE).into_iter();
InnerTestServer::add_cookies_by_header(&mut self.inner_test_server, cookie_headers)?;
}
let mut response = TestResponse::new(path, parts, response_bytes);
if self.is_expecting_failure {
response = response.assert_status_not_ok();
} else {
response = response.assert_status_ok();
}
Ok(response)
}
}
impl IntoFuture for TestRequest {
type Output = TestResponse;
type IntoFuture = AutoFuture<TestResponse>;
fn into_future(self) -> Self::IntoFuture {
let raw_future = self.send_or_panic();
AutoFuture::new(raw_future)
}
}
fn build_content_type_header(content_type: String) -> Result<(HeaderName, HeaderValue)> {
let header_value = HeaderValue::from_str(&content_type)
.with_context(|| format!("Failed to store header content type '{}'", content_type))?;
Ok((header::CONTENT_TYPE, header_value))
}