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