Skip to main content

tork_core/testing/
response.rs

1//! The response returned by the test client.
2
3use bytes::Bytes;
4use http::{HeaderMap, StatusCode};
5use serde::de::DeserializeOwned;
6
7use crate::error::{Error, Result};
8
9/// A buffered response captured by the [`TestClient`](super::TestClient).
10///
11/// The body is fully read when the request is sent, so the accessors are cheap and
12/// can be called repeatedly.
13pub struct TestResponse {
14    pub(crate) status: StatusCode,
15    pub(crate) headers: HeaderMap,
16    pub(crate) body: Bytes,
17}
18
19impl TestResponse {
20    /// Returns the status code as a number.
21    pub fn status(&self) -> u16 {
22        self.status.as_u16()
23    }
24
25    /// Returns the status code.
26    pub fn status_code(&self) -> StatusCode {
27        self.status
28    }
29
30    /// Returns the response headers.
31    pub fn headers(&self) -> &HeaderMap {
32        &self.headers
33    }
34
35    /// Returns the raw response body.
36    pub fn bytes(&self) -> Bytes {
37        self.body.clone()
38    }
39
40    /// Returns the response body as UTF-8 text.
41    pub fn text(&self) -> Result<String> {
42        String::from_utf8(self.body.to_vec())
43            .map_err(|_| Error::internal("response body is not valid UTF-8"))
44    }
45
46    /// Deserializes the response body as JSON.
47    pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
48        serde_json::from_slice(&self.body)
49            .map_err(|error| Error::internal(format!("response body is not valid JSON: {error}")))
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use bytes::Bytes;
57    use http::HeaderValue;
58    use serde::Deserialize;
59
60    fn response(body: &[u8]) -> TestResponse {
61        let mut headers = HeaderMap::new();
62        headers.insert("x-test", HeaderValue::from_static("ok"));
63        TestResponse {
64            status: StatusCode::CREATED,
65            headers,
66            body: Bytes::copy_from_slice(body),
67        }
68    }
69
70    #[derive(Debug, Deserialize, PartialEq)]
71    struct Payload {
72        ok: bool,
73    }
74
75    #[tokio::test]
76    async fn exposes_status_headers_bytes_and_json() {
77        let response = response(br#"{"ok":true}"#);
78
79        assert_eq!(response.status(), 201);
80        assert_eq!(response.status_code(), StatusCode::CREATED);
81        assert_eq!(response.headers()["x-test"], "ok");
82        assert_eq!(response.bytes(), Bytes::from_static(br#"{"ok":true}"#));
83        assert_eq!(response.text().unwrap(), r#"{"ok":true}"#);
84        assert_eq!(
85            response.json::<Payload>().await.unwrap(),
86            Payload { ok: true }
87        );
88    }
89
90    #[test]
91    fn text_rejects_invalid_utf8() {
92        let response = response(&[0xff, 0xfe]);
93
94        let error = response.text().unwrap_err();
95        assert_eq!(error.message(), "response body is not valid UTF-8");
96    }
97
98    #[tokio::test]
99    async fn json_rejects_invalid_payload() {
100        let response = response(br#"{"ok":"wrong"}"#);
101
102        let error = response.json::<Payload>().await.unwrap_err();
103        assert!(error
104            .message()
105            .starts_with("response body is not valid JSON:"));
106    }
107}