trillium_testing/
test_conn.rs

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