use axum::{
Router,
body::Bytes,
extract::Query,
routing::{get, post},
};
use http::HeaderMap;
use nidus_testing::{TestApp, TestRequestError};
use serde::{Serialize, ser};
#[derive(Debug, serde::Deserialize, Serialize)]
struct SearchQuery {
name: String,
page: u32,
}
struct BrokenJsonBody;
impl Serialize for BrokenJsonBody {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
Err(ser::Error::custom("broken json body"))
}
}
#[tokio::test]
async fn test_request_sends_custom_headers() {
let router = Router::new().route(
"/echo-header",
post(|headers: HeaderMap| async move {
headers
.get("x-api-key")
.and_then(|value| value.to_str().ok())
.unwrap_or("missing")
.to_owned()
}),
);
let response = TestApp::from_router(router)
.post("/echo-header")
.header("x-api-key", "secret")
.send()
.await;
response.assert_text("secret");
}
#[test]
fn test_request_try_header_reports_invalid_headers() {
let router = Router::new();
let invalid_name = TestApp::from_router(router.clone())
.get("/health")
.try_header("bad header", "secret");
assert!(invalid_name.is_err());
let invalid_value = TestApp::from_router(router)
.get("/health")
.try_header("x-api-key", "bad\nvalue");
assert!(invalid_value.is_err());
}
#[test]
fn test_request_try_json_reports_serialization_errors() {
let router = Router::new();
let error = match TestApp::from_router(router)
.post("/users")
.try_json(&BrokenJsonBody)
{
Ok(_) => panic!("broken JSON body should fail to serialize"),
Err(error) => error,
};
assert_eq!(error.to_string(), "broken json body");
}
#[tokio::test]
async fn test_request_try_send_reports_request_build_errors() {
let router = Router::new();
let error = match TestApp::from_router(router).get("bad uri").try_send().await {
Ok(_) => panic!("invalid request URI should fail"),
Err(error) => error,
};
assert!(matches!(error, TestRequestError::Request(_)));
}
#[tokio::test]
async fn test_request_sends_text_body_with_content_type() {
let router = Router::new().route(
"/echo-text",
post(|headers: HeaderMap, body: String| async move {
let content_type = headers
.get(http::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.unwrap_or("missing");
format!("{content_type}:{body}")
}),
);
let response = TestApp::from_router(router)
.post("/echo-text")
.text("hello")
.send()
.await;
response.assert_text("text/plain; charset=utf-8:hello");
}
#[tokio::test]
async fn test_request_sends_raw_body() {
let router = Router::new().route(
"/bytes",
post(|body: Bytes| async move { body.len().to_string() }),
);
let response = TestApp::from_router(router)
.post("/bytes")
.body(Bytes::from_static(b"nidus"))
.send()
.await;
response.assert_text("5");
}
#[tokio::test]
async fn test_request_appends_typed_query_params() {
let router =
Router::new().route(
"/users",
get(|Query(query): Query<SearchQuery>| async move {
format!("{}:{}", query.name, query.page)
}),
);
let response = TestApp::from_router(router)
.get("/users")
.query(&SearchQuery {
name: "Ada Lovelace".to_owned(),
page: 2,
})
.send()
.await;
response.assert_text("Ada Lovelace:2");
}