1#![allow(clippy::module_inception)]
73#![allow(clippy::derivable_impls)]
74#![allow(clippy::manual_range_contains)]
75#![forbid(unsafe_code)]
76#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
77
78pub(crate) mod internals;
79
80pub mod multipart;
81
82pub mod transport_layer;
83pub mod util;
84
85mod test_request;
86pub use self::test_request::*;
87
88mod test_response;
89pub use self::test_response::*;
90
91mod test_server_builder;
92pub use self::test_server_builder::*;
93
94mod test_server_config;
95pub use self::test_server_config::*;
96
97mod test_server;
98pub use self::test_server::*;
99
100#[cfg(feature = "ws")]
101mod test_web_socket;
102#[cfg(feature = "ws")]
103pub use self::test_web_socket::*;
104#[cfg(feature = "ws")]
105pub use tokio_tungstenite::tungstenite::Message as WsMessage;
106
107mod transport;
108pub use self::transport::*;
109
110pub mod expect_json;
111
112pub use http;
113
114#[cfg(test)]
115mod integrated_test_cookie_saving {
116 use super::*;
117
118 use axum::extract::Request;
119 use axum::routing::get;
120 use axum::routing::post;
121 use axum::routing::put;
122 use axum::Router;
123 use axum_extra::extract::cookie::Cookie as AxumCookie;
124 use axum_extra::extract::cookie::CookieJar;
125 use cookie::time::OffsetDateTime;
126 use cookie::Cookie;
127 use http_body_util::BodyExt;
128 use std::time::Duration;
129
130 const TEST_COOKIE_NAME: &'static str = &"test-cookie";
131
132 async fn get_cookie(cookies: CookieJar) -> (CookieJar, String) {
133 let cookie = cookies.get(&TEST_COOKIE_NAME);
134 let cookie_value = cookie
135 .map(|c| c.value().to_string())
136 .unwrap_or_else(|| "cookie-not-found".to_string());
137
138 (cookies, cookie_value)
139 }
140
141 async fn put_cookie(mut cookies: CookieJar, request: Request) -> (CookieJar, &'static str) {
142 let body_bytes = request
143 .into_body()
144 .collect()
145 .await
146 .expect("Should extract the body")
147 .to_bytes();
148 let body_text: String = String::from_utf8_lossy(&body_bytes).to_string();
149 let cookie = AxumCookie::new(TEST_COOKIE_NAME, body_text);
150 cookies = cookies.add(cookie);
151
152 (cookies, &"done")
153 }
154
155 async fn post_expire_cookie(mut cookies: CookieJar) -> (CookieJar, &'static str) {
156 let mut cookie = AxumCookie::new(TEST_COOKIE_NAME, "expired".to_string());
157 let expired_time = OffsetDateTime::now_utc() - Duration::from_secs(1);
158 cookie.set_expires(expired_time);
159 cookies = cookies.add(cookie);
160
161 (cookies, &"done")
162 }
163
164 fn new_test_router() -> Router {
165 Router::new()
166 .route("/cookie", put(put_cookie))
167 .route("/cookie", get(get_cookie))
168 .route("/expire", post(post_expire_cookie))
169 }
170
171 #[tokio::test]
172 async fn it_should_not_pass_cookies_created_back_up_to_server_by_default() {
173 let server = TestServer::new(new_test_router()).expect("Should create test server");
175
176 server.put(&"/cookie").text(&"new-cookie").await;
178
179 let response_text = server.get(&"/cookie").await.text();
181
182 assert_eq!(response_text, "cookie-not-found");
183 }
184
185 #[tokio::test]
186 async fn it_should_not_pass_cookies_created_back_up_to_server_when_turned_off() {
187 let server = TestServer::builder()
189 .do_not_save_cookies()
190 .build(new_test_router())
191 .expect("Should create test server");
192
193 server.put(&"/cookie").text(&"new-cookie").await;
195
196 let response_text = server.get(&"/cookie").await.text();
198
199 assert_eq!(response_text, "cookie-not-found");
200 }
201
202 #[tokio::test]
203 async fn it_should_pass_cookies_created_back_up_to_server_automatically() {
204 let server = TestServer::builder()
206 .save_cookies()
207 .build(new_test_router())
208 .expect("Should create test server");
209
210 server.put(&"/cookie").text(&"cookie-found!").await;
212
213 let response_text = server.get(&"/cookie").await.text();
215
216 assert_eq!(response_text, "cookie-found!");
217 }
218
219 #[tokio::test]
220 async fn it_should_pass_cookies_created_back_up_to_server_when_turned_on_for_request() {
221 let server = TestServer::builder()
223 .do_not_save_cookies() .build(new_test_router())
225 .expect("Should create test server");
226
227 server
229 .put(&"/cookie")
230 .text(&"cookie-found!")
231 .save_cookies()
232 .await;
233
234 let response_text = server.get(&"/cookie").await.text();
236
237 assert_eq!(response_text, "cookie-found!");
238 }
239
240 #[tokio::test]
241 async fn it_should_wipe_cookies_cleared_by_request() {
242 let server = TestServer::builder()
244 .do_not_save_cookies() .build(new_test_router())
246 .expect("Should create test server");
247
248 server
250 .put(&"/cookie")
251 .text(&"cookie-found!")
252 .save_cookies()
253 .await;
254
255 let response_text = server.get(&"/cookie").clear_cookies().await.text();
257
258 assert_eq!(response_text, "cookie-not-found");
259 }
260
261 #[tokio::test]
262 async fn it_should_wipe_cookies_cleared_by_test_server() {
263 let mut server = TestServer::builder()
265 .do_not_save_cookies() .build(new_test_router())
267 .expect("Should create test server");
268
269 server
271 .put(&"/cookie")
272 .text(&"cookie-found!")
273 .save_cookies()
274 .await;
275
276 server.clear_cookies();
277
278 let response_text = server.get(&"/cookie").await.text();
280
281 assert_eq!(response_text, "cookie-not-found");
282 }
283
284 #[tokio::test]
285 async fn it_should_send_cookies_added_to_request() {
286 let server = TestServer::builder()
288 .do_not_save_cookies() .build(new_test_router())
290 .expect("Should create test server");
291
292 let cookie = Cookie::new(TEST_COOKIE_NAME, "my-custom-cookie");
294
295 let response_text = server.get(&"/cookie").add_cookie(cookie).await.text();
296
297 assert_eq!(response_text, "my-custom-cookie");
298 }
299
300 #[tokio::test]
301 async fn it_should_send_cookies_added_to_test_server() {
302 let mut server = TestServer::builder()
304 .do_not_save_cookies() .build(new_test_router())
306 .expect("Should create test server");
307
308 let cookie = Cookie::new(TEST_COOKIE_NAME, "my-custom-cookie");
310 server.add_cookie(cookie);
311
312 let response_text = server.get(&"/cookie").await.text();
313
314 assert_eq!(response_text, "my-custom-cookie");
315 }
316
317 #[tokio::test]
318 async fn it_should_remove_expired_cookies_from_later_requests() {
319 let mut server = TestServer::new(new_test_router()).expect("Should create test server");
321 server.save_cookies();
322
323 server.put(&"/cookie").text(&"cookie-found!").await;
325
326 let response_text = server.get(&"/cookie").await.text();
328 assert_eq!(response_text, "cookie-found!");
329
330 server.post(&"/expire").await;
331
332 let found_cookie = server.post(&"/expire").await.maybe_cookie(TEST_COOKIE_NAME);
334 assert!(found_cookie.is_some());
335
336 let response_text = server.get(&"/cookie").await.text();
338 assert_eq!(response_text, "cookie-not-found");
339 }
340}
341
342#[cfg(feature = "typed-routing")]
343#[cfg(test)]
344mod integrated_test_typed_routing_and_query {
345 use super::*;
346
347 use axum::extract::Query;
348 use axum::Router;
349 use axum_extra::routing::RouterExt;
350 use axum_extra::routing::TypedPath;
351 use serde::Deserialize;
352 use serde::Serialize;
353
354 #[derive(TypedPath, Deserialize)]
355 #[typed_path("/path-query/{id}")]
356 struct TestingPathQuery {
357 id: u32,
358 }
359
360 #[derive(Serialize, Deserialize)]
361 struct QueryParams {
362 param: String,
363 other: Option<String>,
364 }
365
366 async fn route_get_with_param(
367 TestingPathQuery { id }: TestingPathQuery,
368 Query(params): Query<QueryParams>,
369 ) -> String {
370 let query = params.param;
371 if let Some(other) = params.other {
372 format!("get {id}, {query}&{other}")
373 } else {
374 format!("get {id}, {query}")
375 }
376 }
377
378 fn new_app() -> Router {
379 Router::new().typed_get(route_get_with_param)
380 }
381
382 #[tokio::test]
383 async fn it_should_send_typed_get_with_query_params() {
384 let server = TestServer::new(new_app()).unwrap();
385 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
386 param: "with-typed-query".to_string(),
387 other: None,
388 });
389
390 server
391 .typed_get(&path)
392 .expect_success()
393 .await
394 .assert_text("get 123, with-typed-query");
395 }
396
397 #[tokio::test]
398 async fn it_should_send_typed_get_with_added_query_param() {
399 let server = TestServer::new(new_app()).unwrap();
400 let path = TestingPathQuery { id: 123 };
401
402 server
403 .typed_get(&path)
404 .add_query_param("param", "with-added-query")
405 .expect_success()
406 .await
407 .assert_text("get 123, with-added-query");
408 }
409
410 #[tokio::test]
411 async fn it_should_send_both_typed_and_added_query() {
412 let server = TestServer::new(new_app()).unwrap();
413 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
414 param: "with-typed-query".to_string(),
415 other: None,
416 });
417
418 server
419 .typed_get(&path)
420 .add_query_param("other", "with-added-query")
421 .expect_success()
422 .await
423 .assert_text("get 123, with-typed-query&with-added-query");
424 }
425
426 #[tokio::test]
427 async fn it_should_send_replaced_query_when_cleared() {
428 let server = TestServer::new(new_app()).unwrap();
429 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
430 param: "with-typed-query".to_string(),
431 other: Some("with-typed-other".to_string()),
432 });
433
434 server
435 .typed_get(&path)
436 .clear_query_params()
437 .add_query_param("param", "with-added-query")
438 .expect_success()
439 .await
440 .assert_text("get 123, with-added-query");
441 }
442}