servers/http/
test_helpers.rs1use std::convert::TryFrom;
34use std::net::SocketAddr;
35
36use axum::Router;
37use bytes::Bytes;
38use common_telemetry::info;
39use http::header::{HeaderName, HeaderValue};
40use http::{Method, StatusCode};
41use tokio::net::TcpListener;
42
43pub struct TestClient {
45 client: reqwest::Client,
46 addr: SocketAddr,
47}
48
49impl TestClient {
50 pub async fn new(svc: Router) -> Self {
52 let listener = TcpListener::bind("127.0.0.1:0")
53 .await
54 .expect("Could not bind ephemeral socket");
55 let addr = listener.local_addr().unwrap();
56 info!("Listening on {}", addr);
57
58 tokio::spawn(async move {
59 axum::serve(listener, svc).await.expect("server error");
60 });
61
62 let client = reqwest::Client::builder()
63 .redirect(reqwest::redirect::Policy::none())
64 .build()
65 .unwrap();
66
67 TestClient { client, addr }
68 }
69
70 pub fn base_url(&self) -> String {
75 format!("http://{}", self.addr)
76 }
77
78 pub fn get(&self, url: &str) -> RequestBuilder {
80 common_telemetry::info!("GET {} {}", self.addr, url);
81
82 RequestBuilder {
83 builder: self.client.get(format!("http://{}{}", self.addr, url)),
84 }
85 }
86
87 pub fn head(&self, url: &str) -> RequestBuilder {
89 common_telemetry::info!("HEAD {} {}", self.addr, url);
90
91 RequestBuilder {
92 builder: self.client.head(format!("http://{}{}", self.addr, url)),
93 }
94 }
95
96 pub fn post(&self, url: &str) -> RequestBuilder {
98 common_telemetry::info!("POST {} {}", self.addr, url);
99
100 RequestBuilder {
101 builder: self.client.post(format!("http://{}{}", self.addr, url)),
102 }
103 }
104
105 pub fn put(&self, url: &str) -> RequestBuilder {
107 common_telemetry::info!("PUT {} {}", self.addr, url);
108
109 RequestBuilder {
110 builder: self.client.put(format!("http://{}{}", self.addr, url)),
111 }
112 }
113
114 pub fn patch(&self, url: &str) -> RequestBuilder {
116 common_telemetry::info!("PATCH {} {}", self.addr, url);
117
118 RequestBuilder {
119 builder: self.client.patch(format!("http://{}{}", self.addr, url)),
120 }
121 }
122
123 pub fn delete(&self, url: &str) -> RequestBuilder {
125 common_telemetry::info!("DELETE {} {}", self.addr, url);
126
127 RequestBuilder {
128 builder: self.client.delete(format!("http://{}{}", self.addr, url)),
129 }
130 }
131
132 pub fn options(&self, url: &str) -> RequestBuilder {
134 common_telemetry::info!("OPTIONS {} {}", self.addr, url);
135
136 RequestBuilder {
137 builder: self
138 .client
139 .request(Method::OPTIONS, format!("http://{}{}", self.addr, url)),
140 }
141 }
142}
143
144pub struct RequestBuilder {
146 builder: reqwest::RequestBuilder,
147}
148
149impl RequestBuilder {
150 pub async fn send(self) -> TestResponse {
151 TestResponse {
152 response: self.builder.send().await.unwrap(),
153 }
154 }
155
156 pub fn body(mut self, body: impl Into<reqwest::Body>) -> Self {
158 self.builder = self.builder.body(body);
159 self
160 }
161
162 pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
164 self.builder = self.builder.form(&form);
165 self
166 }
167
168 pub fn json<T>(mut self, json: &T) -> Self
170 where
171 T: serde::Serialize,
172 {
173 self.builder = self.builder.json(json);
174 self
175 }
176
177 pub fn header<K, V>(mut self, key: K, value: V) -> Self
179 where
180 HeaderName: TryFrom<K>,
181 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
182 HeaderValue: TryFrom<V>,
183 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
184 {
185 self.builder = self.builder.header(key, value);
186
187 self
188 }
189
190 pub fn multipart(mut self, form: reqwest::multipart::Form) -> Self {
192 self.builder = self.builder.multipart(form);
193 self
194 }
195}
196
197#[derive(Debug)]
203pub struct TestResponse {
204 response: reqwest::Response,
205}
206
207impl TestResponse {
208 pub async fn text(self) -> String {
210 self.response.text().await.unwrap()
211 }
212
213 pub async fn bytes(self) -> Bytes {
215 self.response.bytes().await.unwrap()
216 }
217
218 pub async fn json<T>(self) -> T
220 where
221 T: serde::de::DeserializeOwned,
222 {
223 self.response.json().await.unwrap()
224 }
225
226 pub fn status(&self) -> StatusCode {
228 StatusCode::from_u16(self.response.status().as_u16()).unwrap()
229 }
230
231 pub fn headers(&self) -> http::HeaderMap {
233 self.response.headers().clone()
234 }
235
236 pub async fn chunk(&mut self) -> Option<Bytes> {
238 self.response.chunk().await.unwrap()
239 }
240
241 pub async fn chunk_text(&mut self) -> Option<String> {
243 let chunk = self.chunk().await?;
244 Some(String::from_utf8(chunk.to_vec()).unwrap())
245 }
246
247 pub fn into_inner(self) -> reqwest::Response {
249 self.response
250 }
251}
252
253impl AsRef<reqwest::Response> for TestResponse {
254 fn as_ref(&self) -> &reqwest::Response {
255 &self.response
256 }
257}