use bytes::Bytes;
use hyper::header::{HeaderName, HeaderValue};
use hyper::{HeaderMap, Method, StatusCode, Uri, Version};
use serde::de::DeserializeOwned;
use std::str::FromStr;
pub use reinhardt_http::{Error, Request, Response, Result};
pub fn create_request(
method: Method,
path: &str,
body: Option<String>,
headers: Vec<(&str, &str)>,
) -> Request {
let uri = path.parse::<Uri>().expect("Invalid URI");
let body_bytes = body.map(Bytes::from).unwrap_or_default();
let mut header_map = HeaderMap::new();
for (key, value) in headers {
let header_name: hyper::header::HeaderName = key.parse().expect("Invalid header name");
let header_value: hyper::header::HeaderValue = value.parse().expect("Invalid header value");
header_map.insert(header_name, header_value);
}
Request::builder()
.method(method)
.uri(uri)
.version(Version::HTTP_11)
.headers(header_map)
.body(body_bytes)
.build()
.expect("Failed to build request")
}
pub fn extract_json<T: DeserializeOwned>(response: Response) -> Result<T> {
serde_json::from_slice(&response.body)
.map_err(|e| Error::Serialization(format!("Failed to deserialize response: {}", e)))
}
pub fn create_test_request(method: &str, uri: &str, secure: bool) -> Request {
let method = Method::from_str(method).unwrap_or(Method::GET);
let uri = Uri::from_str(uri).unwrap_or_else(|_| Uri::from_static("/"));
let mut headers = HeaderMap::new();
if secure {
headers.insert(
HeaderName::from_static("x-forwarded-proto"),
HeaderValue::from_static("https"),
);
}
let mut request = Request::builder()
.method(method)
.uri(uri)
.version(Version::HTTP_11)
.headers(headers)
.body(Bytes::new())
.build()
.expect("Failed to build request");
request.is_secure = secure;
request
}
pub fn create_secure_request(method: &str, uri: &str) -> Request {
create_test_request(method, uri, true)
}
pub fn create_insecure_request(method: &str, uri: &str) -> Request {
create_test_request(method, uri, false)
}
pub fn create_test_response() -> Response {
Response::ok()
}
pub fn create_response_with_status(status: StatusCode) -> Response {
Response::new(status)
}
pub fn create_response_with_headers(headers: HeaderMap) -> Response {
let mut response = Response::ok();
response.headers = headers;
response
}
pub fn has_header(response: &Response, header_name: &str) -> bool {
response.headers.contains_key(header_name)
}
pub fn get_header<'a>(response: &'a Response, header_name: &str) -> Option<&'a str> {
response
.headers
.get(header_name)
.and_then(|v| v.to_str().ok())
}
pub fn header_equals(response: &Response, header_name: &str, expected_value: &str) -> bool {
get_header(response, header_name)
.map(|v| v == expected_value)
.unwrap_or(false)
}
pub fn header_contains(response: &Response, header_name: &str, substring: &str) -> bool {
get_header(response, header_name)
.map(|v| v.contains(substring))
.unwrap_or(false)
}
pub fn assert_status(response: &Response, expected: StatusCode) {
assert_eq!(
response.status, expected,
"Expected status {}, got {}",
expected, response.status
);
}
pub fn assert_has_header(response: &Response, header_name: &str) {
assert!(
has_header(response, header_name),
"Expected response to have header '{}'",
header_name
);
}
pub fn assert_no_header(response: &Response, header_name: &str) {
assert!(
!has_header(response, header_name),
"Expected response to NOT have header '{}'",
header_name
);
}
pub fn assert_header_equals(response: &Response, header_name: &str, expected_value: &str) {
let actual = get_header(response, header_name)
.unwrap_or_else(|| panic!("Header '{}' not found", header_name));
assert_eq!(
actual, expected_value,
"Expected header '{}' to be '{}', got '{}'",
header_name, expected_value, actual
);
}
pub fn assert_header_contains(response: &Response, header_name: &str, substring: &str) {
let actual = get_header(response, header_name)
.unwrap_or_else(|| panic!("Header '{}' not found", header_name));
assert!(
actual.contains(substring),
"Expected header '{}' to contain '{}', got '{}'",
header_name,
substring,
actual
);
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn test_create_request_get_basic() {
let request = create_request(Method::GET, "/api/users", None, vec![]);
assert_eq!(request.method, Method::GET);
assert_eq!(request.uri.path(), "/api/users");
assert!(request.body().is_empty());
}
#[rstest]
fn test_create_request_post_with_body() {
let body = r#"{"name": "Alice"}"#;
let request = create_request(Method::POST, "/api/users", Some(body.to_string()), vec![]);
assert_eq!(request.method, Method::POST);
assert_eq!(request.body().len(), body.len());
}
#[rstest]
fn test_create_request_with_headers() {
let headers = vec![
("Content-Type", "application/json"),
("X-API-Key", "secret"),
];
let request = create_request(Method::GET, "/api/users", None, headers);
assert!(request.headers.contains_key("content-type"));
assert!(request.headers.contains_key("x-api-key"));
}
#[rstest]
fn test_create_test_request_secure() {
let request = create_test_request("POST", "/api/login", true);
assert!(request.is_secure);
assert!(request.headers.contains_key("x-forwarded-proto"));
assert_eq!(request.method, Method::POST);
}
#[rstest]
fn test_create_test_request_insecure() {
let request = create_test_request("GET", "/api/users", false);
assert!(!request.is_secure);
assert!(!request.headers.contains_key("x-forwarded-proto"));
}
#[rstest]
fn test_create_secure_request() {
let request = create_secure_request("GET", "/api/users");
assert!(request.is_secure);
assert_eq!(request.method, Method::GET);
}
#[rstest]
fn test_create_insecure_request() {
let request = create_insecure_request("GET", "/api/users");
assert!(!request.is_secure);
assert_eq!(request.method, Method::GET);
}
#[rstest]
fn test_create_test_response() {
let response = create_test_response();
assert_eq!(response.status, StatusCode::OK);
}
#[rstest]
fn test_create_response_with_status() {
let response = create_response_with_status(StatusCode::NOT_FOUND);
assert_eq!(response.status, StatusCode::NOT_FOUND);
}
#[rstest]
fn test_create_response_with_headers() {
let mut headers = HeaderMap::new();
headers.insert(
HeaderName::from_static("x-custom-header"),
HeaderValue::from_static("custom-value"),
);
let response = create_response_with_headers(headers);
assert!(response.headers.contains_key("x-custom-header"));
}
#[rstest]
fn test_extract_json_valid() {
#[derive(serde::Deserialize, PartialEq, Debug)]
struct User {
id: i64,
name: String,
}
let response = Response::ok()
.with_header("Content-Type", "application/json")
.with_body(r#"{"id": 1, "name": "Alice"}"#);
let user: User = extract_json(response).unwrap();
assert_eq!(user.id, 1);
assert_eq!(user.name, "Alice");
}
#[rstest]
fn test_extract_json_invalid() {
#[derive(serde::Deserialize)]
struct User {
#[allow(dead_code)] id: i64,
}
let response = Response::ok().with_body("not json");
let result: Result<User> = extract_json(response);
assert!(result.is_err());
}
#[rstest]
fn test_has_header_present() {
let response = create_test_response().with_header("x-api-version", "v1");
assert!(has_header(&response, "x-api-version"));
}
#[rstest]
fn test_has_header_absent() {
let response = create_test_response();
assert!(!has_header(&response, "x-missing"));
}
#[rstest]
fn test_get_header_present() {
let response = create_test_response().with_header("x-api-version", "v1");
let value = get_header(&response, "x-api-version");
assert_eq!(value, Some("v1"));
}
#[rstest]
fn test_get_header_absent() {
let response = create_test_response();
let value = get_header(&response, "x-missing");
assert_eq!(value, None);
}
#[rstest]
fn test_header_equals_match() {
let response = create_test_response().with_header("content-type", "application/json");
assert!(header_equals(&response, "content-type", "application/json"));
}
#[rstest]
fn test_header_equals_mismatch() {
let response = create_test_response().with_header("content-type", "application/json");
assert!(!header_equals(&response, "content-type", "text/html"));
}
#[rstest]
fn test_header_contains_substring() {
let response =
create_test_response().with_header("content-type", "application/json; charset=utf-8");
assert!(header_contains(
&response,
"content-type",
"application/json"
));
assert!(header_contains(&response, "content-type", "charset"));
}
#[rstest]
fn test_header_contains_no_match() {
let response = create_test_response().with_header("content-type", "application/json");
assert!(!header_contains(&response, "content-type", "text/html"));
}
#[rstest]
fn test_assert_status_pass() {
let response = create_test_response();
assert_status(&response, StatusCode::OK);
}
#[rstest]
#[should_panic(expected = "Expected status")]
fn test_assert_status_fail() {
let response = create_test_response();
assert_status(&response, StatusCode::NOT_FOUND);
}
#[rstest]
fn test_assert_has_header_pass() {
let response = create_test_response().with_header("x-api-version", "v1");
assert_has_header(&response, "x-api-version");
}
#[rstest]
#[should_panic(expected = "Expected response to have header")]
fn test_assert_has_header_fail() {
let response = create_test_response();
assert_has_header(&response, "x-missing");
}
#[rstest]
fn test_assert_no_header_pass() {
let response = create_test_response();
assert_no_header(&response, "x-missing");
}
#[rstest]
#[should_panic(expected = "Expected response to NOT have header")]
fn test_assert_no_header_fail() {
let response = create_test_response().with_header("x-api-version", "v1");
assert_no_header(&response, "x-api-version");
}
}