Skip to main content

nidus_testing/
response.rs

1use axum::body::Bytes;
2use http::{HeaderMap, HeaderValue, StatusCode, header::ToStrError};
3use serde::de::DeserializeOwned;
4use serde_json::Value;
5use std::str;
6
7/// Captured in-memory HTTP response.
8///
9/// `TestResponse` owns the status, headers, and collected body returned by
10/// [`crate::TestRequest::send`]. Assertion helpers panic with ordinary test
11/// assertion failures, while `try_json`, `text`, and `header_str` expose
12/// fallible parsing.
13///
14/// ```
15/// # use axum::{Json, Router, routing::get};
16/// use http::StatusCode;
17/// # use nidus_testing::TestApp;
18/// use serde_json::json;
19/// # #[tokio::main]
20/// # async fn main() -> Result<(), http::header::ToStrError> {
21/// # let app = TestApp::from_router(
22/// #     Router::new().route("/health", get(|| async { Json(json!({ "ok": true })) })),
23/// # );
24///
25/// let response = app.get("/health").send().await;
26/// response.assert_status(StatusCode::OK);
27/// assert_eq!(response.header_str("content-type")?.unwrap(), "application/json");
28/// response.assert_json(json!({ "ok": true }));
29/// # Ok(())
30/// # }
31/// ```
32pub struct TestResponse {
33    status: StatusCode,
34    headers: HeaderMap,
35    body: Bytes,
36}
37
38impl TestResponse {
39    pub(crate) fn new(status: StatusCode, headers: HeaderMap, body: Bytes) -> Self {
40        Self {
41            status,
42            headers,
43            body,
44        }
45    }
46
47    /// Returns the response status code.
48    pub fn status(&self) -> StatusCode {
49        self.status
50    }
51
52    /// Returns the raw response body bytes.
53    pub fn body(&self) -> &[u8] {
54        &self.body
55    }
56
57    /// Returns the response headers.
58    pub fn headers(&self) -> &HeaderMap {
59        &self.headers
60    }
61
62    /// Returns a response header by name.
63    pub fn header(&self, name: impl AsRef<str>) -> Option<&HeaderValue> {
64        self.headers.get(name.as_ref())
65    }
66
67    /// Returns a response header as a UTF-8 string when present.
68    pub fn header_str(&self, name: impl AsRef<str>) -> Result<Option<&str>, ToStrError> {
69        self.header(name).map(HeaderValue::to_str).transpose()
70    }
71
72    /// Asserts the response status code.
73    pub fn assert_status(&self, expected: StatusCode) {
74        assert_eq!(self.status, expected);
75    }
76
77    /// Asserts a response header as a UTF-8 string.
78    pub fn assert_header(&self, name: impl AsRef<str>, expected: &str) {
79        let name = name.as_ref();
80        let actual = self
81            .header(name)
82            .unwrap_or_else(|| panic!("missing response header `{name}`"));
83        assert_eq!(
84            actual
85                .to_str()
86                .unwrap_or_else(|_| panic!("response header `{name}` was not valid UTF-8")),
87            expected
88        );
89    }
90
91    /// Decodes the response body as JSON.
92    ///
93    /// Panics when the body is not valid JSON for `T`. Use [`Self::try_json`]
94    /// when asserting malformed responses.
95    pub fn json<T>(&self) -> T
96    where
97        T: DeserializeOwned,
98    {
99        self.try_json().expect("test response was not valid JSON")
100    }
101
102    /// Tries to decode the response body as JSON.
103    pub fn try_json<T>(&self) -> serde_json::Result<T>
104    where
105        T: DeserializeOwned,
106    {
107        serde_json::from_slice(&self.body)
108    }
109
110    /// Returns the response body as UTF-8 text.
111    pub fn text(&self) -> std::result::Result<&str, str::Utf8Error> {
112        str::from_utf8(&self.body)
113    }
114
115    /// Asserts the response body as UTF-8 text.
116    ///
117    /// This consumes the response so tests cannot accidentally assert against a
118    /// body after moving it into another helper.
119    pub fn assert_text(self, expected: &str) {
120        let text = self.text().expect("test response was not UTF-8");
121        assert_eq!(text, expected);
122    }
123
124    /// Asserts the response body as JSON.
125    ///
126    /// This consumes the response and compares against a `serde_json::Value`.
127    pub fn assert_json(self, expected: Value) {
128        let actual: Value = self.json();
129        assert_eq!(actual, expected);
130    }
131}