Skip to main content

cratefield_testing/
request.rs

1//! The no-network request helper (issue #9): `tower::ServiceExt::oneshot`
2//! straight into the router.
3
4use axum::body::Body;
5use axum::http::{HeaderValue, Method, Request, StatusCode, header};
6use axum::response::Response;
7use bytes::Bytes;
8use serde_json::Value;
9
10/// Sends a request through the router without a network. `json` (when
11/// `Some`) becomes a JSON body with `content-type: application/json`.
12///
13/// # Panics
14///
15/// Panics when the router itself fails (never for ordinary responses).
16pub async fn request(
17    router: &axum::Router,
18    method: Method,
19    path: &str,
20    json: Option<&str>,
21) -> TestResponse {
22    use tower::ServiceExt;
23    let mut builder = Request::builder().method(method).uri(path);
24    let body = match json {
25        Some(payload) => {
26            builder = builder.header(
27                header::CONTENT_TYPE,
28                HeaderValue::from_static("application/json"),
29            );
30            Body::from(payload.to_owned())
31        }
32        None => Body::empty(),
33    };
34    let request = builder.body(body).expect("request builds");
35    let response = router
36        .clone()
37        .oneshot(request)
38        .await
39        .expect("router answers");
40    TestResponse::from(response).await
41}
42
43/// A fully-buffered test response.
44pub struct TestResponse {
45    pub status: StatusCode,
46    pub headers: axum::http::HeaderMap,
47    body: Bytes,
48}
49
50impl TestResponse {
51    /// Reads a response the caller drove itself (the conformance kit's
52    /// parity probes build their own requests).
53    pub(crate) async fn of(response: Response) -> Self {
54        Self::from(response).await
55    }
56
57    async fn from(response: Response) -> Self {
58        let (parts, body) = response.into_parts();
59        let body = axum::body::to_bytes(body, 1024 * 1024)
60            .await
61            .expect("test body reads");
62        Self {
63            status: parts.status,
64            headers: parts.headers,
65            body,
66        }
67    }
68
69    /// The body parsed as JSON.
70    ///
71    /// # Panics
72    ///
73    /// Panics when the body is not valid JSON.
74    #[must_use]
75    pub fn json(&self) -> Value {
76        serde_json::from_slice(&self.body).expect("body is JSON")
77    }
78
79    /// The raw body.
80    #[must_use]
81    pub fn body(&self) -> &Bytes {
82        &self.body
83    }
84}