use http::{Method, HeaderMap, HeaderName, HeaderValue};
use http_body_util::Full;
use bytes::Bytes;
use serde::Serialize;
use oxidite_core::types::OxiditeRequest;
use http_body_util::BodyExt;
#[derive(Debug, thiserror::Error)]
pub enum TestRequestError {
#[error("invalid header name: {0}")]
InvalidHeaderName(#[from] http::header::InvalidHeaderName),
#[error("invalid header value: {0}")]
InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
#[error("failed to serialize JSON body: {0}")]
JsonSerialize(#[from] serde_json::Error),
#[error("failed to build request: {0}")]
Build(#[from] http::Error),
}
pub struct TestRequest {
method: Method,
uri: String,
headers: HeaderMap,
body: Vec<u8>,
}
impl TestRequest {
pub fn new(method: Method, uri: impl Into<String>) -> Self {
Self {
method,
uri: uri.into(),
headers: HeaderMap::new(),
body: Vec::new(),
}
}
pub fn get(uri: impl Into<String>) -> Self {
Self::new(Method::GET, uri)
}
pub fn post(uri: impl Into<String>) -> Self {
Self::new(Method::POST, uri)
}
pub fn put(uri: impl Into<String>) -> Self {
Self::new(Method::PUT, uri)
}
pub fn delete(uri: impl Into<String>) -> Self {
Self::new(Method::DELETE, uri)
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
let name = HeaderName::from_bytes(name.into().as_bytes())
.expect("invalid header name in TestRequest::header");
let value = HeaderValue::from_str(&value.into())
.expect("invalid header value in TestRequest::header");
self.headers.insert(name, value);
self
}
pub fn try_header(
mut self,
name: impl Into<String>,
value: impl Into<String>,
) -> Result<Self, TestRequestError> {
let name = HeaderName::from_bytes(name.into().as_bytes())?;
let value = HeaderValue::from_str(&value.into())?;
self.headers.insert(name, value);
Ok(self)
}
pub fn json<T: Serialize>(mut self, body: &T) -> Self {
self.body = serde_json::to_vec(body).expect("failed to serialize JSON body in TestRequest::json");
self = self.header("content-type", "application/json");
self
}
pub fn try_json<T: Serialize>(mut self, body: &T) -> Result<Self, TestRequestError> {
self.body = serde_json::to_vec(body)?;
self.try_header("content-type", "application/json")
}
pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
self.body = body.into();
self
}
pub fn build(self) -> http::Request<Full<Bytes>> {
let mut builder = http::Request::builder()
.method(self.method)
.uri(self.uri);
for (name, value) in self.headers.iter() {
builder = builder.header(name, value);
}
builder
.body(Full::new(Bytes::from(self.body)))
.expect("failed to build http::Request in TestRequest::build")
}
pub fn try_build(self) -> Result<http::Request<Full<Bytes>>, TestRequestError> {
let mut builder = http::Request::builder()
.method(self.method)
.uri(self.uri);
for (name, value) in &self.headers {
builder = builder.header(name, value);
}
Ok(builder.body(Full::new(Bytes::from(self.body)))?)
}
pub fn build_oxidite(self) -> OxiditeRequest {
let request = self.build();
let (parts, body) = request.into_parts();
let body = body.map_err(|e| match e {}).boxed();
http::Request::from_parts(parts, body)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Serialize, Deserialize)]
struct TestData {
name: String,
}
#[test]
fn test_request_builder() {
let data = TestData {
name: "test".to_string(),
};
let request = TestRequest::post("/api/test")
.json(&data)
.header("x-custom", "value")
.build();
assert_eq!(request.method(), Method::POST);
assert_eq!(request.uri(), "/api/test");
assert!(request.headers().contains_key("content-type"));
}
#[test]
fn test_try_header_invalid_name() {
let result = TestRequest::get("/").try_header("bad header", "value");
assert!(result.is_err());
}
#[test]
fn test_build_oxidite() {
let request = TestRequest::get("/health").build_oxidite();
assert_eq!(request.uri(), "/health");
}
}