Skip to main content

trillium_testing/
test_conn.rs

1use std::{
2    fmt::Debug,
3    net::IpAddr,
4    ops::{Deref, DerefMut},
5    sync::Arc,
6};
7use trillium::{Conn, Handler, HeaderName, HeaderValues, Method, Transport};
8use trillium_http::{Conn as HttpConn, ServerConfig, Synthetic};
9
10type SyntheticConn = HttpConn<Synthetic>;
11
12/// A wrapper around a [`trillium::Conn`] for testing
13///
14/// Stability note: this may be replaced by an extension trait at some point.
15#[derive(Debug)]
16pub struct TestConn(Conn);
17
18impl TestConn {
19    /// constructs a new TestConn with the provided method, path, and body.
20    /// ```
21    /// use trillium_testing::{TestConn, prelude::*};
22    /// let mut conn = TestConn::build("get", "/", "body");
23    /// assert_eq!(conn.method(), Method::Get);
24    /// assert_eq!(conn.path(), "/");
25    /// assert_eq!(conn.take_request_body_string(), "body");
26    /// ```
27    pub fn build<M>(method: M, path: impl Into<String>, body: impl Into<Synthetic>) -> Self
28    where
29        M: TryInto<Method>,
30        <M as TryInto<Method>>::Error: Debug,
31    {
32        Self(HttpConn::new_synthetic(method.try_into().unwrap(), path.into(), body).into())
33    }
34
35    /// assigns a shared server config to this test conn
36    pub fn with_server_config(self, server_config: Arc<ServerConfig>) -> Self {
37        let inner = self
38            .0
39            .into_inner::<Synthetic>()
40            .with_server_config(server_config)
41            .into();
42
43        Self(inner)
44    }
45
46    /// chainable constructor to append a request header to the TestConn
47    /// ```
48    /// use trillium_testing::TestConn;
49    /// let conn = TestConn::build("get", "/", "body").with_request_header("some-header", "value");
50    /// assert_eq!(conn.request_headers().get_str("some-header"), Some("value"));
51    /// ```
52    pub fn with_request_header(
53        self,
54        header_name: impl Into<HeaderName<'static>>,
55        header_value: impl Into<HeaderValues>,
56    ) -> Self {
57        let mut inner: SyntheticConn = self.into();
58        inner
59            .request_headers_mut()
60            .append(header_name, header_value);
61        Self(inner.into())
62    }
63
64    /// chainable constructor to replace the request body. this is useful
65    /// when chaining with a [`trillium_testing::methods`](crate::methods)
66    /// builder, as they do not provide a way to specify the body.
67    ///
68    /// ```
69    /// use trillium_testing::{TestConn, methods::post};
70    /// let mut conn = post("/").with_request_body("some body");
71    /// assert_eq!(conn.take_request_body_string(), "some body");
72    ///
73    /// let mut conn = TestConn::build("post", "/", "some body").with_request_body("once told me");
74    /// assert_eq!(conn.take_request_body_string(), "once told me");
75    /// ```
76    pub fn with_request_body(self, body: impl Into<Synthetic>) -> Self {
77        let mut inner: SyntheticConn = self.into();
78        inner.replace_body(body);
79        Self(inner.into())
80    }
81
82    /// sets the peer ip for this test conn
83    pub fn with_peer_ip(mut self, ip: IpAddr) -> Self {
84        self.set_peer_ip(Some(ip));
85        self
86    }
87
88    /// set the test conn to be secure
89    pub fn secure(mut self) -> Self {
90        AsMut::<trillium_http::Conn<Box<dyn Transport>>>::as_mut(&mut self.0).set_secure(true);
91        self
92    }
93
94    /// set state on the test conn
95    pub fn with_state<S>(mut self, state: S) -> Self
96    where
97        S: Send + Sync + 'static,
98    {
99        self.0.insert_state(state);
100        self
101    }
102
103    /// blocks on running this conn against a handler and finalizes
104    /// response headers. also aliased as [`TestConn::on`]
105    ///
106    /// ```
107    /// use trillium_testing::prelude::*;
108    ///
109    /// async fn handler(conn: Conn) -> Conn {
110    /// conn.ok("hello trillium")
111    /// }
112    ///
113    /// let conn = get("/").run(&handler);
114    /// assert_ok!(conn, "hello trillium", "content-length" => "14");
115    /// ```
116    pub fn run(self, handler: &impl Handler) -> Self {
117        crate::block_on(self.run_async(handler))
118    }
119
120    /// runs this conn against a handler and finalizes
121    /// response headers.
122    ///
123    /// ```
124    /// use trillium_testing::prelude::*;
125    ///
126    /// async fn handler(conn: Conn) -> Conn {
127    /// conn.ok("hello trillium")
128    /// }
129    ///
130    /// block_on(async move {
131    /// let conn = get("/").run_async(&handler).await;
132    /// assert_ok!(conn, "hello trillium", "content-length" => "14");
133    /// });
134    /// ```
135    pub async fn run_async(self, handler: &impl Handler) -> Self {
136        let conn = handler.run(self.into()).await;
137        let mut conn = handler.before_send(conn).await;
138        AsMut::<trillium_http::Conn<Box<dyn Transport>>>::as_mut(&mut conn).finalize_headers();
139        Self(conn)
140    }
141
142    /// blocks on running this conn against a handler and finalizes
143    /// response headers. also aliased as [`TestConn::run`].
144    ///
145    /// ```
146    /// use trillium_testing::prelude::*;
147    ///
148    /// async fn handler(conn: Conn) -> Conn {
149    /// conn.ok("hello trillium")
150    /// }
151    ///
152    /// let conn = get("/").on(&handler);
153    /// assert_ok!(conn, "hello trillium", "content-length" => "14");
154    /// ```
155    pub fn on(self, handler: &impl Handler) -> Self {
156        self.run(handler)
157    }
158
159    /// Reads the response body to string and returns it, if set.
160    pub fn take_response_body_string(&mut self) -> Option<String> {
161        match self.take_response_body() {
162            Some(body) => String::from_utf8(
163                futures_lite::future::block_on(body.into_bytes())
164                    .unwrap()
165                    .to_vec(),
166            )
167            .ok(),
168            _ => None,
169        }
170    }
171
172    /// Reads the request body to string and returns it
173    pub fn take_request_body_string(&mut self) -> String {
174        futures_lite::future::block_on(async {
175            self.request_body().await.read_string().await.unwrap()
176        })
177    }
178}
179
180impl From<Conn> for TestConn {
181    fn from(conn: Conn) -> Self {
182        Self(conn)
183    }
184}
185
186impl From<TestConn> for Conn {
187    fn from(tc: TestConn) -> Self {
188        tc.0
189    }
190}
191
192impl From<TestConn> for SyntheticConn {
193    fn from(tc: TestConn) -> Self {
194        tc.0.into_inner()
195    }
196}
197
198impl Deref for TestConn {
199    type Target = Conn;
200
201    fn deref(&self) -> &Self::Target {
202        &self.0
203    }
204}
205
206impl DerefMut for TestConn {
207    fn deref_mut(&mut self) -> &mut Self::Target {
208        &mut self.0
209    }
210}