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/// The same, carrying `Authorization: Bearer <token>`.
11///
12/// Every venture with a table whose access is not `public-read` needs
13/// this, and so does every admin-guarded route — and the kit did not have
14/// it, so three crates in this workspace each hand-rolled the same
15/// builder over `tower::ServiceExt`. A venture cannot: `tower` is not one
16/// of its dependencies, and telling an author to add one to test the
17/// routes the harness generated is telling them to work around the kit.
18///
19/// # Panics
20///
21/// Panics when the router itself fails (never for ordinary responses).
22pub async fn request_as(
23    router: &axum::Router,
24    method: Method,
25    path: &str,
26    bearer: &str,
27    json: Option<&str>,
28) -> TestResponse {
29    use tower::ServiceExt;
30    let mut builder = Request::builder()
31        .method(method)
32        .uri(path)
33        .header(header::AUTHORIZATION, format!("Bearer {bearer}"));
34    let body = match json {
35        Some(payload) => {
36            builder = builder.header(
37                header::CONTENT_TYPE,
38                HeaderValue::from_static("application/json"),
39            );
40            Body::from(payload.to_owned())
41        }
42        None => Body::empty(),
43    };
44    let request = builder.body(body).expect("request builds");
45    let response = router
46        .clone()
47        .oneshot(request)
48        .await
49        .expect("router answers");
50    TestResponse::from(response).await
51}
52
53/// Sends a request through the router without a network. `json` (when
54/// `Some`) becomes a JSON body with `content-type: application/json`.
55///
56/// # Panics
57///
58/// Panics when the router itself fails (never for ordinary responses).
59pub async fn request(
60    router: &axum::Router,
61    method: Method,
62    path: &str,
63    json: Option<&str>,
64) -> TestResponse {
65    use tower::ServiceExt;
66    let mut builder = Request::builder().method(method).uri(path);
67    let body = match json {
68        Some(payload) => {
69            builder = builder.header(
70                header::CONTENT_TYPE,
71                HeaderValue::from_static("application/json"),
72            );
73            Body::from(payload.to_owned())
74        }
75        None => Body::empty(),
76    };
77    let request = builder.body(body).expect("request builds");
78    let response = router
79        .clone()
80        .oneshot(request)
81        .await
82        .expect("router answers");
83    TestResponse::from(response).await
84}
85
86/// A fully-buffered test response.
87pub struct TestResponse {
88    pub status: StatusCode,
89    pub headers: axum::http::HeaderMap,
90    body: Bytes,
91}
92
93impl TestResponse {
94    /// Reads a response the caller drove itself (the conformance kit's
95    /// parity probes build their own requests).
96    pub(crate) async fn of(response: Response) -> Self {
97        Self::from(response).await
98    }
99
100    async fn from(response: Response) -> Self {
101        let (parts, body) = response.into_parts();
102        let body = axum::body::to_bytes(body, 1024 * 1024)
103            .await
104            .expect("test body reads");
105        Self {
106            status: parts.status,
107            headers: parts.headers,
108            body,
109        }
110    }
111
112    /// The body parsed as JSON.
113    ///
114    /// # Panics
115    ///
116    /// Panics when the body is not valid JSON.
117    #[must_use]
118    pub fn json(&self) -> Value {
119        serde_json::from_slice(&self.body).expect("body is JSON")
120    }
121
122    /// The raw body.
123    #[must_use]
124    pub fn body(&self) -> &Bytes {
125        &self.body
126    }
127}