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}