1#![allow(clippy::module_inception)]
111#![allow(clippy::collapsible_if)]
112#![allow(clippy::derivable_impls)]
113#![allow(clippy::manual_range_contains)]
114#![forbid(unsafe_code)]
115#![cfg_attr(docsrs, feature(doc_cfg))]
116
117pub(crate) mod internals;
118
119pub mod multipart;
120
121pub mod transport_layer;
122pub mod util;
123
124mod test_request;
125pub use self::test_request::*;
126
127mod test_response;
128pub use self::test_response::*;
129
130mod test_server_builder;
131pub use self::test_server_builder::*;
132
133mod test_server_config;
134pub use self::test_server_config::*;
135
136mod test_server;
137pub use self::test_server::*;
138
139#[cfg(feature = "ws")]
140mod test_web_socket;
141#[cfg(feature = "ws")]
142pub use self::test_web_socket::*;
143#[cfg(feature = "ws")]
144pub use tokio_tungstenite::tungstenite::Message as WsMessage;
145
146mod transport;
147pub use self::transport::*;
148
149pub mod expect_json;
150
151pub use http;
152
153#[cfg(test)]
154mod testing;
155
156#[cfg(test)]
157mod integrated_test_cookie_saving {
158 use super::*;
159 use axum::Router;
160 use axum::extract::Request;
161 use axum::routing::get;
162 use axum::routing::post;
163 use axum::routing::put;
164 use axum_extra::extract::cookie::Cookie as AxumCookie;
165 use axum_extra::extract::cookie::CookieJar;
166 use cookie::Cookie;
167 use cookie::time::OffsetDateTime;
168 use http_body_util::BodyExt;
169 use std::time::Duration;
170
171 const TEST_COOKIE_NAME: &'static str = &"test-cookie";
172
173 async fn get_cookie(cookies: CookieJar) -> (CookieJar, String) {
174 let cookie = cookies.get(&TEST_COOKIE_NAME);
175 let cookie_value = cookie
176 .map(|c| c.value().to_string())
177 .unwrap_or_else(|| "cookie-not-found".to_string());
178
179 (cookies, cookie_value)
180 }
181
182 async fn put_cookie(mut cookies: CookieJar, request: Request) -> (CookieJar, &'static str) {
183 let body_bytes = request
184 .into_body()
185 .collect()
186 .await
187 .expect("Should extract the body")
188 .to_bytes();
189 let body_text: String = String::from_utf8_lossy(&body_bytes).to_string();
190 let cookie = AxumCookie::new(TEST_COOKIE_NAME, body_text);
191 cookies = cookies.add(cookie);
192
193 (cookies, &"done")
194 }
195
196 async fn post_expire_cookie(mut cookies: CookieJar) -> (CookieJar, &'static str) {
197 let mut cookie = AxumCookie::new(TEST_COOKIE_NAME, "expired".to_string());
198 let expired_time = OffsetDateTime::now_utc() - Duration::from_secs(1);
199 cookie.set_expires(expired_time);
200 cookies = cookies.add(cookie);
201
202 (cookies, &"done")
203 }
204
205 fn new_test_router() -> Router {
206 Router::new()
207 .route("/cookie", put(put_cookie))
208 .route("/cookie", get(get_cookie))
209 .route("/expire", post(post_expire_cookie))
210 }
211
212 #[tokio::test]
213 async fn it_should_not_pass_cookies_created_back_up_to_server_by_default() {
214 let server = TestServer::new(new_test_router());
216
217 server.put(&"/cookie").text(&"new-cookie").await;
219
220 let response_text = server.get(&"/cookie").await.text();
222
223 assert_eq!(response_text, "cookie-not-found");
224 }
225
226 #[tokio::test]
227 async fn it_should_not_pass_cookies_created_back_up_to_server_when_turned_off() {
228 let server = TestServer::builder()
230 .do_not_save_cookies()
231 .build(new_test_router());
232
233 server.put(&"/cookie").text(&"new-cookie").await;
235
236 let response_text = server.get(&"/cookie").await.text();
238
239 assert_eq!(response_text, "cookie-not-found");
240 }
241
242 #[tokio::test]
243 async fn it_should_pass_cookies_created_back_up_to_server_automatically() {
244 let server = TestServer::builder()
246 .save_cookies()
247 .build(new_test_router());
248
249 server.put(&"/cookie").text(&"cookie-found!").await;
251
252 let response_text = server.get(&"/cookie").await.text();
254
255 assert_eq!(response_text, "cookie-found!");
256 }
257
258 #[tokio::test]
259 async fn it_should_pass_cookies_created_back_up_to_server_when_turned_on_for_request() {
260 let server = TestServer::builder()
262 .do_not_save_cookies() .build(new_test_router());
264
265 server
267 .put(&"/cookie")
268 .text(&"cookie-found!")
269 .save_cookies()
270 .await;
271
272 let response_text = server.get(&"/cookie").await.text();
274
275 assert_eq!(response_text, "cookie-found!");
276 }
277
278 #[tokio::test]
279 async fn it_should_wipe_cookies_cleared_by_request() {
280 let server = TestServer::builder()
282 .do_not_save_cookies() .build(new_test_router());
284
285 server
287 .put(&"/cookie")
288 .text(&"cookie-found!")
289 .save_cookies()
290 .await;
291
292 let response_text = server.get(&"/cookie").clear_cookies().await.text();
294
295 assert_eq!(response_text, "cookie-not-found");
296 }
297
298 #[tokio::test]
299 async fn it_should_wipe_cookies_cleared_by_test_server() {
300 let mut server = TestServer::builder()
302 .do_not_save_cookies() .build(new_test_router());
304
305 server
307 .put(&"/cookie")
308 .text(&"cookie-found!")
309 .save_cookies()
310 .await;
311
312 server.clear_cookies();
313
314 let response_text = server.get(&"/cookie").await.text();
316
317 assert_eq!(response_text, "cookie-not-found");
318 }
319
320 #[tokio::test]
321 async fn it_should_send_cookies_added_to_request() {
322 let server = TestServer::builder()
324 .do_not_save_cookies() .build(new_test_router());
326
327 let cookie = Cookie::new(TEST_COOKIE_NAME, "my-custom-cookie");
329
330 let response_text = server.get(&"/cookie").add_cookie(cookie).await.text();
331
332 assert_eq!(response_text, "my-custom-cookie");
333 }
334
335 #[tokio::test]
336 async fn it_should_send_cookies_added_to_test_server() {
337 let mut server = TestServer::builder()
339 .do_not_save_cookies() .build(new_test_router());
341
342 let cookie = Cookie::new(TEST_COOKIE_NAME, "my-custom-cookie");
344 server.add_cookie(cookie);
345
346 let response_text = server.get(&"/cookie").await.text();
347
348 assert_eq!(response_text, "my-custom-cookie");
349 }
350
351 #[tokio::test]
352 async fn it_should_remove_expired_cookies_from_later_requests() {
353 let mut server = TestServer::new(new_test_router());
355 server.save_cookies();
356
357 server.put(&"/cookie").text(&"cookie-found!").await;
359
360 let response_text = server.get(&"/cookie").await.text();
362 assert_eq!(response_text, "cookie-found!");
363
364 server.post(&"/expire").await;
365
366 let found_cookie = server.post(&"/expire").await.maybe_cookie(TEST_COOKIE_NAME);
368 assert!(found_cookie.is_some());
369
370 let response_text = server.get(&"/cookie").await.text();
372 assert_eq!(response_text, "cookie-not-found");
373 }
374}
375
376#[cfg(feature = "typed-routing")]
377#[cfg(test)]
378mod integrated_test_typed_routing_and_query {
379 use super::*;
380 use axum::Router;
381 use axum::extract::Query;
382 use axum_extra::routing::RouterExt;
383 use axum_extra::routing::TypedPath;
384 use serde::Deserialize;
385 use serde::Serialize;
386
387 #[derive(TypedPath, Deserialize)]
388 #[typed_path("/path-query/{id}")]
389 struct TestingPathQuery {
390 id: u32,
391 }
392
393 #[derive(Serialize, Deserialize)]
394 struct QueryParams {
395 param: String,
396 other: Option<String>,
397 }
398
399 async fn route_get_with_param(
400 TestingPathQuery { id }: TestingPathQuery,
401 Query(params): Query<QueryParams>,
402 ) -> String {
403 let query = params.param;
404 if let Some(other) = params.other {
405 format!("get {id}, {query}&{other}")
406 } else {
407 format!("get {id}, {query}")
408 }
409 }
410
411 fn new_app() -> Router {
412 Router::new().typed_get(route_get_with_param)
413 }
414
415 #[tokio::test]
416 async fn it_should_send_typed_get_with_query_params() {
417 let server = TestServer::new(new_app());
418 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
419 param: "with-typed-query".to_string(),
420 other: None,
421 });
422
423 server
424 .typed_get(&path)
425 .expect_success()
426 .await
427 .assert_text("get 123, with-typed-query");
428 }
429
430 #[tokio::test]
431 async fn it_should_send_typed_get_with_added_query_param() {
432 let server = TestServer::new(new_app());
433 let path = TestingPathQuery { id: 123 };
434
435 server
436 .typed_get(&path)
437 .add_query_param("param", "with-added-query")
438 .expect_success()
439 .await
440 .assert_text("get 123, with-added-query");
441 }
442
443 #[tokio::test]
444 async fn it_should_send_both_typed_and_added_query() {
445 let server = TestServer::new(new_app());
446 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
447 param: "with-typed-query".to_string(),
448 other: None,
449 });
450
451 server
452 .typed_get(&path)
453 .add_query_param("other", "with-added-query")
454 .expect_success()
455 .await
456 .assert_text("get 123, with-typed-query&with-added-query");
457 }
458
459 #[tokio::test]
460 async fn it_should_send_replaced_query_when_cleared() {
461 let server = TestServer::new(new_app());
462 let path = TestingPathQuery { id: 123 }.with_query_params(QueryParams {
463 param: "with-typed-query".to_string(),
464 other: Some("with-typed-other".to_string()),
465 });
466
467 server
468 .typed_get(&path)
469 .clear_query_params()
470 .add_query_param("param", "with-added-query")
471 .expect_success()
472 .await
473 .assert_text("get 123, with-added-query");
474 }
475}