servers/http/
test_helpers.rs1use std::convert::TryFrom;
48use std::net::SocketAddr;
49
50use axum::Router;
51use bytes::Bytes;
52use common_telemetry::info;
53use http::header::{HeaderName, HeaderValue};
54use http::{Method, StatusCode};
55use tokio::net::TcpListener;
56
57pub struct TestClient {
59 client: reqwest::Client,
60 addr: SocketAddr,
61}
62
63impl TestClient {
64 pub async fn new(svc: Router) -> Self {
66 let listener = TcpListener::bind("127.0.0.1:0")
67 .await
68 .expect("Could not bind ephemeral socket");
69 let addr = listener.local_addr().unwrap();
70 info!("Listening on {}", addr);
71
72 tokio::spawn(async move {
73 axum::serve(listener, svc).await.expect("server error");
74 });
75
76 let client = reqwest::Client::builder()
77 .redirect(reqwest::redirect::Policy::none())
78 .build()
79 .unwrap();
80
81 TestClient { client, addr }
82 }
83
84 pub fn base_url(&self) -> String {
89 format!("http://{}", self.addr)
90 }
91
92 pub fn get(&self, url: &str) -> RequestBuilder {
94 common_telemetry::info!("GET {} {}", self.addr, url);
95
96 RequestBuilder {
97 builder: self.client.get(format!("http://{}{}", self.addr, url)),
98 }
99 }
100
101 pub fn head(&self, url: &str) -> RequestBuilder {
103 common_telemetry::info!("HEAD {} {}", self.addr, url);
104
105 RequestBuilder {
106 builder: self.client.head(format!("http://{}{}", self.addr, url)),
107 }
108 }
109
110 pub fn post(&self, url: &str) -> RequestBuilder {
112 common_telemetry::info!("POST {} {}", self.addr, url);
113
114 RequestBuilder {
115 builder: self.client.post(format!("http://{}{}", self.addr, url)),
116 }
117 }
118
119 pub fn put(&self, url: &str) -> RequestBuilder {
121 common_telemetry::info!("PUT {} {}", self.addr, url);
122
123 RequestBuilder {
124 builder: self.client.put(format!("http://{}{}", self.addr, url)),
125 }
126 }
127
128 pub fn patch(&self, url: &str) -> RequestBuilder {
130 common_telemetry::info!("PATCH {} {}", self.addr, url);
131
132 RequestBuilder {
133 builder: self.client.patch(format!("http://{}{}", self.addr, url)),
134 }
135 }
136
137 pub fn delete(&self, url: &str) -> RequestBuilder {
139 common_telemetry::info!("DELETE {} {}", self.addr, url);
140
141 RequestBuilder {
142 builder: self.client.delete(format!("http://{}{}", self.addr, url)),
143 }
144 }
145
146 pub fn options(&self, url: &str) -> RequestBuilder {
148 common_telemetry::info!("OPTIONS {} {}", self.addr, url);
149
150 RequestBuilder {
151 builder: self
152 .client
153 .request(Method::OPTIONS, format!("http://{}{}", self.addr, url)),
154 }
155 }
156}
157
158pub struct RequestBuilder {
160 builder: reqwest::RequestBuilder,
161}
162
163impl RequestBuilder {
164 pub async fn send(self) -> TestResponse {
165 TestResponse {
166 response: self.builder.send().await.unwrap(),
167 }
168 }
169
170 pub fn body(mut self, body: impl Into<reqwest::Body>) -> Self {
172 self.builder = self.builder.body(body);
173 self
174 }
175
176 pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
178 self.builder = self.builder.form(&form);
179 self
180 }
181
182 pub fn json<T>(mut self, json: &T) -> Self
184 where
185 T: serde::Serialize,
186 {
187 self.builder = self.builder.json(json);
188 self
189 }
190
191 pub fn header<K, V>(mut self, key: K, value: V) -> Self
193 where
194 HeaderName: TryFrom<K>,
195 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
196 HeaderValue: TryFrom<V>,
197 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
198 {
199 self.builder = self.builder.header(key, value);
200
201 self
202 }
203
204 pub fn multipart(mut self, form: reqwest::multipart::Form) -> Self {
206 self.builder = self.builder.multipart(form);
207 self
208 }
209}
210
211#[derive(Debug)]
217pub struct TestResponse {
218 response: reqwest::Response,
219}
220
221impl TestResponse {
222 pub async fn text(self) -> String {
224 self.response.text().await.unwrap()
225 }
226
227 pub async fn bytes(self) -> Bytes {
229 self.response.bytes().await.unwrap()
230 }
231
232 pub async fn json<T>(self) -> T
234 where
235 T: serde::de::DeserializeOwned,
236 {
237 self.response.json().await.unwrap()
238 }
239
240 pub fn status(&self) -> StatusCode {
242 StatusCode::from_u16(self.response.status().as_u16()).unwrap()
243 }
244
245 pub fn headers(&self) -> http::HeaderMap {
247 self.response.headers().clone()
248 }
249
250 pub async fn chunk(&mut self) -> Option<Bytes> {
252 self.response.chunk().await.unwrap()
253 }
254
255 pub async fn chunk_text(&mut self) -> Option<String> {
257 let chunk = self.chunk().await?;
258 Some(String::from_utf8(chunk.to_vec()).unwrap())
259 }
260
261 pub fn into_inner(self) -> reqwest::Response {
263 self.response
264 }
265}
266
267impl AsRef<reqwest::Response> for TestResponse {
268 fn as_ref(&self) -> &reqwest::Response {
269 &self.response
270 }
271}