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 use http;
111
112#[cfg(test)]
113mod integrated_test_cookie_saving {
114 use super::*;
115
116 use axum::extract::Request;
117 use axum::routing::get;
118 use axum::routing::post;
119 use axum::routing::put;
120 use axum::Router;
121 use axum_extra::extract::cookie::Cookie as AxumCookie;
122 use axum_extra::extract::cookie::CookieJar;
123 use cookie::time::OffsetDateTime;
124 use cookie::Cookie;
125 use http_body_util::BodyExt;
126 use std::time::Duration;
127
128 const TEST_COOKIE_NAME: &'static str = &"test-cookie";
129
130 async fn get_cookie(cookies: CookieJar) -> (CookieJar, String) {
131 let cookie = cookies.get(&TEST_COOKIE_NAME);
132 let cookie_value = cookie
133 .map(|c| c.value().to_string())
134 .unwrap_or_else(|| "cookie-not-found".to_string());
135
136 (cookies, cookie_value)
137 }
138
139 async fn put_cookie(mut cookies: CookieJar, request: Request) -> (CookieJar, &'static str) {
140 let body_bytes = request
141 .into_body()
142 .collect()
143 .await
144 .expect("Should extract the body")
145 .to_bytes();
146 let body_text: String = String::from_utf8_lossy(&body_bytes).to_string();
147 let cookie = AxumCookie::new(TEST_COOKIE_NAME, body_text);
148 cookies = cookies.add(cookie);
149
150 (cookies, &"done")
151 }
152
153 async fn post_expire_cookie(mut cookies: CookieJar) -> (CookieJar, &'static str) {
154 let mut cookie = AxumCookie::new(TEST_COOKIE_NAME, "expired".to_string());
155 let expired_time = OffsetDateTime::now_utc() - Duration::from_secs(1);
156 cookie.set_expires(expired_time);
157 cookies = cookies.add(cookie);
158
159 (cookies, &"done")
160 }
161
162 fn new_test_router() -> Router {
163 Router::new()
164 .route("/cookie", put(put_cookie))
165 .route("/cookie", get(get_cookie))
166 .route("/expire", post(post_expire_cookie))
167 }
168
169 #[tokio::test]
170 async fn it_should_not_pass_cookies_created_back_up_to_server_by_default() {
171 let server = TestServer::new(new_test_router()).expect("Should create test server");
173
174 server.put(&"/cookie").text(&"new-cookie").await;
176
177 let response_text = server.get(&"/cookie").await.text();
179
180 assert_eq!(response_text, "cookie-not-found");
181 }
182
183 #[tokio::test]
184 async fn it_should_not_pass_cookies_created_back_up_to_server_when_turned_off() {
185 let server = TestServer::builder()
187 .do_not_save_cookies()
188 .build(new_test_router())
189 .expect("Should create test server");
190
191 server.put(&"/cookie").text(&"new-cookie").await;
193
194 let response_text = server.get(&"/cookie").await.text();
196
197 assert_eq!(response_text, "cookie-not-found");
198 }
199
200 #[tokio::test]
201 async fn it_should_pass_cookies_created_back_up_to_server_automatically() {
202 let server = TestServer::builder()
204 .save_cookies()
205 .build(new_test_router())
206 .expect("Should create test server");
207
208 server.put(&"/cookie").text(&"cookie-found!").await;
210
211 let response_text = server.get(&"/cookie").await.text();
213
214 assert_eq!(response_text, "cookie-found!");
215 }
216
217 #[tokio::test]
218 async fn it_should_pass_cookies_created_back_up_to_server_when_turned_on_for_request() {
219 let server = TestServer::builder()
221 .do_not_save_cookies() .build(new_test_router())
223 .expect("Should create test server");
224
225 server
227 .put(&"/cookie")
228 .text(&"cookie-found!")
229 .save_cookies()
230 .await;
231
232 let response_text = server.get(&"/cookie").await.text();
234
235 assert_eq!(response_text, "cookie-found!");
236 }
237
238 #[tokio::test]
239 async fn it_should_wipe_cookies_cleared_by_request() {
240 let server = TestServer::builder()
242 .do_not_save_cookies() .build(new_test_router())
244 .expect("Should create test server");
245
246 server
248 .put(&"/cookie")
249 .text(&"cookie-found!")
250 .save_cookies()
251 .await;
252
253 let response_text = server.get(&"/cookie").clear_cookies().await.text();
255
256 assert_eq!(response_text, "cookie-not-found");
257 }
258
259 #[tokio::test]
260 async fn it_should_wipe_cookies_cleared_by_test_server() {
261 let mut server = TestServer::builder()
263 .do_not_save_cookies() .build(new_test_router())
265 .expect("Should create test server");
266
267 server
269 .put(&"/cookie")
270 .text(&"cookie-found!")
271 .save_cookies()
272 .await;
273
274 server.clear_cookies();
275
276 let response_text = server.get(&"/cookie").await.text();
278
279 assert_eq!(response_text, "cookie-not-found");
280 }
281
282 #[tokio::test]
283 async fn it_should_send_cookies_added_to_request() {
284 let server = TestServer::builder()
286 .do_not_save_cookies() .build(new_test_router())
288 .expect("Should create test server");
289
290 let cookie = Cookie::new(TEST_COOKIE_NAME, "my-custom-cookie");
292
293 let response_text = server.get(&"/cookie").add_cookie(cookie).await.text();
294
295 assert_eq!(response_text, "my-custom-cookie");
296 }
297
298 #[tokio::test]
299 async fn it_should_send_cookies_added_to_test_server() {
300 let mut server = TestServer::builder()
302 .do_not_save_cookies() .build(new_test_router())
304 .expect("Should create test server");
305
306 let cookie = Cookie::new(TEST_COOKIE_NAME, "my-custom-cookie");
308 server.add_cookie(cookie);
309
310 let response_text = server.get(&"/cookie").await.text();
311
312 assert_eq!(response_text, "my-custom-cookie");
313 }
314
315 #[tokio::test]
316 async fn it_should_remove_expired_cookies_from_later_requests() {
317 let mut server = TestServer::new(new_test_router()).expect("Should create test server");
319 server.save_cookies();
320
321 server.put(&"/cookie").text(&"cookie-found!").await;
323
324 let response_text = server.get(&"/cookie").await.text();
326 assert_eq!(response_text, "cookie-found!");
327
328 server.post(&"/expire").await;
329
330 let found_cookie = server.post(&"/expire").await.maybe_cookie(TEST_COOKIE_NAME);
332 assert!(found_cookie.is_some());
333
334 let response_text = server.get(&"/cookie").await.text();
336 assert_eq!(response_text, "cookie-not-found");
337 }
338}
339
340#[cfg(feature = "typed-routing")]
341#[cfg(test)]
342mod integrated_test_typed_routing_and_query {
343 use super::*;
344
345 use axum::extract::Query;
346 use axum::Router;
347 use axum_extra::routing::RouterExt;
348 use axum_extra::routing::TypedPath;
349 use serde::Deserialize;
350 use serde::Serialize;
351
352 #[derive(TypedPath, Deserialize)]
353 #[typed_path("/path-query/{id}")]
354 struct TestingPathQuery {
355 id: u32,
356 }
357
358 #[derive(Serialize, Deserialize)]
359 struct QueryParams {
360 param: String,
361 other: Option<String>,
362 }
363
364 async fn route_get_with_param(
365 TestingPathQuery { id }: TestingPathQuery,
366 Query(params): Query<QueryParams>,
367 ) -> String {
368 let query = params.param;
369 if let Some(other) = params.other {
370 format!("get {id}, {query}&{other}")
371 } else {
372 format!("get {id}, {query}")
373 }
374 }
375
376 fn new_app() -> Router {
377 Router::new().typed_get(route_get_with_param)
378 }
379
380 #[tokio::test]
381 async fn it_should_send_typed_get_with_query_params() {
382 let server = TestServer::new(new_app()).unwrap();
383 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
384 param: "with-typed-query".to_string(),
385 other: None,
386 });
387
388 server
389 .typed_get(&path)
390 .expect_success()
391 .await
392 .assert_text("get 123, with-typed-query");
393 }
394
395 #[tokio::test]
396 async fn it_should_send_typed_get_with_added_query_param() {
397 let server = TestServer::new(new_app()).unwrap();
398 let path = TestingPathQuery { id: 123 };
399
400 server
401 .typed_get(&path)
402 .add_query_param("param", "with-added-query")
403 .expect_success()
404 .await
405 .assert_text("get 123, with-added-query");
406 }
407
408 #[tokio::test]
409 async fn it_should_send_both_typed_and_added_query() {
410 let server = TestServer::new(new_app()).unwrap();
411 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
412 param: "with-typed-query".to_string(),
413 other: None,
414 });
415
416 server
417 .typed_get(&path)
418 .add_query_param("other", "with-added-query")
419 .expect_success()
420 .await
421 .assert_text("get 123, with-typed-query&with-added-query");
422 }
423
424 #[tokio::test]
425 async fn it_should_send_replaced_query_when_cleared() {
426 let server = TestServer::new(new_app()).unwrap();
427 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
428 param: "with-typed-query".to_string(),
429 other: Some("with-typed-other".to_string()),
430 });
431
432 server
433 .typed_get(&path)
434 .clear_query_params()
435 .add_query_param("param", "with-added-query")
436 .expect_success()
437 .await
438 .assert_text("get 123, with-added-query");
439 }
440}