1use crate::TestRequest;
2use crate::TestRequestConfig;
3use crate::TestServerBuilder;
4use crate::TestServerConfig;
5use crate::Transport;
6use crate::internals::AtomicCrossCookieJar;
7use crate::internals::ErrorMessage;
8use crate::internals::ExpectedState;
9use crate::internals::QueryParamsStore;
10use crate::transport_layer::IntoTransportLayer;
11use crate::transport_layer::TransportLayer;
12use crate::transport_layer::TransportLayerBuilder;
13use anyhow::Result;
14use anyhow::anyhow;
15use cookie::Cookie;
16use cookie::CookieJar;
17use http::HeaderName;
18use http::HeaderValue;
19use http::Method;
20use http::Uri;
21use serde::Serialize;
22use std::fmt::Debug;
23use std::sync::Arc;
24use url::Url;
25
26#[cfg(feature = "typed-routing")]
27use axum_extra::routing::TypedPath;
28
29#[cfg(feature = "reqwest")]
30use crate::transport_layer::TransportLayerType;
31#[cfg(feature = "reqwest")]
32use reqwest::Client;
33#[cfg(feature = "reqwest")]
34use reqwest::RequestBuilder;
35#[cfg(feature = "reqwest")]
36use std::cell::OnceCell;
37
38mod server_shared_state;
39pub(crate) use self::server_shared_state::*;
40
41const DEFAULT_URL_ADDRESS: &str = "http://localhost";
42
43#[derive(Debug)]
142pub struct TestServer {
143 state: ServerSharedState,
144 cookie_jar: Arc<AtomicCrossCookieJar>,
145 transport: Arc<Box<dyn TransportLayer>>,
146 expected_state: ExpectedState,
147 default_content_type: Option<String>,
148 is_http_path_restricted: bool,
149
150 #[cfg(feature = "reqwest")]
151 maybe_reqwest_client: OnceCell<Client>,
152}
153
154impl TestServer {
155 pub fn builder() -> TestServerBuilder {
157 TestServerBuilder::default()
158 }
159
160 pub fn new<A>(app: A) -> Self
195 where
196 A: IntoTransportLayer,
197 {
198 Self::try_new(app).error_message("Failed to build TestServer")
199 }
200
201 pub fn try_new<A>(app: A) -> Result<Self>
203 where
204 A: IntoTransportLayer,
205 {
206 Self::try_new_with_config(app, TestServerConfig::default())
207 }
208
209 pub fn new_with_config<A, C>(app: A, config: C) -> Self
216 where
217 A: IntoTransportLayer,
218 C: Into<TestServerConfig>,
219 {
220 Self::try_new_with_config(app, config).error_message("Failed to build TestServer")
221 }
222
223 pub fn try_new_with_config<A, C>(app: A, config: C) -> Result<Self>
225 where
226 A: IntoTransportLayer,
227 C: Into<TestServerConfig>,
228 {
229 let config = config.into();
230 let state = ServerSharedState::new();
231
232 let transport = match config.transport {
233 None => {
234 let builder = TransportLayerBuilder::from_ip_port(None, None);
235 let transport = app.into_default_transport(builder)?;
236 Arc::new(transport)
237 }
238 Some(Transport::HttpRandomPort) => {
239 let builder = TransportLayerBuilder::from_ip_port(None, None);
240 let transport = app.into_http_transport_layer(builder)?;
241 Arc::new(transport)
242 }
243 Some(Transport::HttpIpPort { ip, port }) => {
244 let builder = TransportLayerBuilder::from_ip_port(ip, port);
245 let transport = app.into_http_transport_layer(builder)?;
246 Arc::new(transport)
247 }
248 Some(Transport::HttpTcpListner { tcp_listener }) => {
249 let builder = TransportLayerBuilder::from_tcp_listener(tcp_listener);
250 let transport = app.into_http_transport_layer(builder)?;
251 Arc::new(transport)
252 }
253 Some(Transport::MockHttp) => {
254 let transport = app.into_mock_transport_layer()?;
255 Arc::new(transport)
256 }
257 };
258
259 let expected_state = match config.expect_success_by_default {
260 true => ExpectedState::Success,
261 false => ExpectedState::None,
262 };
263
264 Ok(Self {
265 state,
266 cookie_jar: Arc::new(AtomicCrossCookieJar::new(config.save_cookies)),
267 transport,
268 expected_state,
269 default_content_type: config.default_content_type,
270 is_http_path_restricted: config.restrict_requests_with_http_scheme,
271
272 #[cfg(feature = "reqwest")]
273 maybe_reqwest_client: Default::default(),
274 })
275 }
276
277 pub fn get(&self, path: &str) -> TestRequest {
279 self.method(Method::GET, path)
280 }
281
282 pub fn post(&self, path: &str) -> TestRequest {
284 self.method(Method::POST, path)
285 }
286
287 pub fn query(&self, path: &str) -> TestRequest {
289 self.method(Method::QUERY, path)
290 }
291
292 pub fn patch(&self, path: &str) -> TestRequest {
294 self.method(Method::PATCH, path)
295 }
296
297 pub fn put(&self, path: &str) -> TestRequest {
299 self.method(Method::PUT, path)
300 }
301
302 pub fn delete(&self, path: &str) -> TestRequest {
304 self.method(Method::DELETE, path)
305 }
306
307 pub fn method(&self, method: Method, path: &str) -> TestRequest {
309 let config = self
310 .build_test_request_config(method.clone(), path)
311 .error_message_fn(|| format!("Failed to build request, for {method} {path}"));
312
313 TestRequest::new(self.transport.clone(), config)
314 }
315
316 #[cfg(feature = "reqwest")]
317 fn reqwest_client(&self) -> &Client {
318 self.maybe_reqwest_client.get_or_init(|| {
319 if self.transport.transport_layer_type() == TransportLayerType::Mock {
320 panic!("Reqwest client is not available, TestServer must be build with HTTP transport for Reqwest to be available");
321 }
322
323 reqwest::Client::builder()
324 .redirect(reqwest::redirect::Policy::none())
325 .cookie_provider(self.cookie_jar.clone())
326 .build()
327 .expect("Failed to build Reqwest Client")
328 })
329 }
330
331 #[cfg(feature = "reqwest")]
332 pub fn reqwest_get(&self, path: &str) -> RequestBuilder {
333 self.reqwest_method(Method::GET, path)
334 }
335
336 #[cfg(feature = "reqwest")]
337 pub fn reqwest_post(&self, path: &str) -> RequestBuilder {
338 self.reqwest_method(Method::POST, path)
339 }
340
341 #[cfg(feature = "reqwest")]
342 pub fn reqwest_put(&self, path: &str) -> RequestBuilder {
343 self.reqwest_method(Method::PUT, path)
344 }
345
346 #[cfg(feature = "reqwest")]
347 pub fn reqwest_patch(&self, path: &str) -> RequestBuilder {
348 self.reqwest_method(Method::PATCH, path)
349 }
350
351 #[cfg(feature = "reqwest")]
352 pub fn reqwest_delete(&self, path: &str) -> RequestBuilder {
353 self.reqwest_method(Method::DELETE, path)
354 }
355
356 #[cfg(feature = "reqwest")]
357 pub fn reqwest_head(&self, path: &str) -> RequestBuilder {
358 self.reqwest_method(Method::HEAD, path)
359 }
360
361 #[cfg(feature = "reqwest")]
386 pub fn reqwest_method(&self, method: Method, path: &str) -> RequestBuilder {
387 let request_url = self
388 .server_url(path)
389 .expect("Failed to generate server url for request {method} {path}");
390
391 self.reqwest_client().request(method, request_url)
392 }
393
394 #[cfg(feature = "ws")]
429 pub fn get_websocket(&self, path: &str) -> TestRequest {
430 use http::header;
431
432 self.get(path)
433 .add_header(header::CONNECTION, "upgrade")
434 .add_header(header::UPGRADE, "websocket")
435 .add_header(header::SEC_WEBSOCKET_VERSION, "13")
436 .add_header(
437 header::SEC_WEBSOCKET_KEY,
438 crate::internals::generate_ws_key(),
439 )
440 }
441
442 #[cfg(feature = "typed-routing")]
489 pub fn typed_get<P>(&self, path: &P) -> TestRequest
490 where
491 P: TypedPath,
492 {
493 self.typed_method(Method::GET, path)
494 }
495
496 #[cfg(feature = "typed-routing")]
500 pub fn typed_post<P>(&self, path: &P) -> TestRequest
501 where
502 P: TypedPath,
503 {
504 self.typed_method(Method::POST, path)
505 }
506
507 #[cfg(feature = "typed-routing")]
511 pub fn typed_patch<P>(&self, path: &P) -> TestRequest
512 where
513 P: TypedPath,
514 {
515 self.typed_method(Method::PATCH, path)
516 }
517
518 #[cfg(feature = "typed-routing")]
522 pub fn typed_put<P>(&self, path: &P) -> TestRequest
523 where
524 P: TypedPath,
525 {
526 self.typed_method(Method::PUT, path)
527 }
528
529 #[cfg(feature = "typed-routing")]
533 pub fn typed_delete<P>(&self, path: &P) -> TestRequest
534 where
535 P: TypedPath,
536 {
537 self.typed_method(Method::DELETE, path)
538 }
539
540 #[cfg(feature = "typed-routing")]
544 pub fn typed_method<P>(&self, method: Method, path: &P) -> TestRequest
545 where
546 P: TypedPath,
547 {
548 self.method(method, &path.to_string())
549 }
550
551 pub fn server_address(&self) -> Option<Url> {
559 self.url()
560 }
561
562 pub fn server_url(&self, path: &str) -> Result<Url> {
595 let path_uri = path.parse::<Uri>()?;
596 if is_absolute_uri(&path_uri) {
597 return Err(anyhow!(
598 "Absolute path provided for building server url, need to provide a relative uri"
599 ));
600 }
601
602 let server_url = self.url()
603 .ok_or_else(||
604 anyhow!(
605 "No local address for server, need to run with HTTP transport to have a server address",
606 )
607 )?;
608
609 let mut query_params = self.state.query_params().clone();
610 let mut full_server_url = build_url(
611 server_url,
612 path,
613 &mut query_params,
614 self.is_http_path_restricted,
615 )?;
616
617 if query_params.has_content() {
619 full_server_url.set_query(Some(&query_params.to_string()));
620 }
621
622 Ok(full_server_url)
623 }
624
625 pub fn add_cookie(&mut self, cookie: Cookie) {
630 self.cookie_jar.add_cookie(cookie);
631 }
632
633 pub fn add_cookies(&mut self, cookies: CookieJar) {
638 self.cookie_jar.add_cookies_by_jar(cookies);
639 }
640
641 pub fn clear_cookies(&mut self) {
643 self.cookie_jar.clear_cookies();
644 }
645
646 pub fn save_cookies(&mut self) {
651 self.cookie_jar.enable_saving();
652 }
653
654 pub fn do_not_save_cookies(&mut self) {
659 self.cookie_jar.disable_saving();
660 }
661
662 pub fn expect_success(&mut self) {
666 self.expected_state = ExpectedState::Success;
667 }
668
669 pub fn expect_failure(&mut self) {
673 self.expected_state = ExpectedState::Failure;
674 }
675
676 pub fn add_query_param<V>(&mut self, key: &str, value: V)
678 where
679 V: Serialize,
680 {
681 self.state
682 .add_query_param(key, value)
683 .error_message("Failed to add query parameter");
684 }
685
686 pub fn add_query_params<V>(&mut self, query_params: V)
688 where
689 V: Serialize,
690 {
691 self.state
692 .add_query_params(query_params)
693 .error_message("Failed to add query parameters");
694 }
695
696 pub fn add_raw_query_param(&mut self, raw_query_param: &str) {
699 self.state.add_raw_query_param(raw_query_param);
700 }
701
702 pub fn clear_query_params(&mut self) {
704 self.state.clear_query_params();
705 }
706
707 pub fn add_header<N, V>(&mut self, name: N, value: V)
728 where
729 N: TryInto<HeaderName>,
730 N::Error: Debug,
731 V: TryInto<HeaderValue>,
732 V::Error: Debug,
733 {
734 let header_name: HeaderName = name
735 .try_into()
736 .expect("Failed to convert header name to HeaderName");
737 let header_value: HeaderValue = value
738 .try_into()
739 .expect("Failed to convert header vlue to HeaderValue");
740
741 self.state.add_header(header_name, header_value);
742 }
743
744 pub fn clear_headers(&mut self) {
746 self.state.clear_headers();
747 }
748
749 pub(crate) fn url(&self) -> Option<Url> {
750 self.transport.url().cloned()
751 }
752
753 pub(crate) fn build_test_request_config(
754 &self,
755 method: Method,
756 path: &str,
757 ) -> Result<TestRequestConfig> {
758 let url = self
759 .url()
760 .unwrap_or_else(|| DEFAULT_URL_ADDRESS.parse().unwrap());
761
762 let mut query_params = self.state.query_params().clone();
763 let headers = self.state.headers().clone();
764 let full_request_url =
765 build_url(url, path, &mut query_params, self.is_http_path_restricted)?;
766
767 Ok(TestRequestConfig {
768 atomic_cookie_jar: self.cookie_jar.clone(),
769
770 is_saving_cookies: self.cookie_jar.is_saving(),
773 cookies: self.cookie_jar.to_cookie_jar(),
774
775 expected_state: self.expected_state,
776 content_type: self.default_content_type.clone(),
777 method,
778
779 full_request_url,
780 query_params,
781 headers,
782 })
783 }
784
785 pub fn is_running(&self) -> bool {
791 self.transport.is_running()
792 }
793}
794
795fn build_url(
796 mut url: Url,
797 path: &str,
798 query_params: &mut QueryParamsStore,
799 is_http_restricted: bool,
800) -> Result<Url> {
801 let path_uri = path.parse::<Uri>()?;
802
803 if let Some(scheme) = path_uri.scheme_str() {
805 if is_http_restricted {
806 if has_different_scheme(&url, &path_uri) || has_different_authority(&url, &path_uri) {
807 return Err(anyhow!(
808 "Request disallowed for path '{path}', requests are only allowed to local server. Turn off 'restrict_requests_with_http_scheme' to change this."
809 ));
810 }
811 } else {
812 url.set_scheme(scheme)
813 .map_err(|_| anyhow!("Failed to set scheme for request, with path '{path}'"))?;
814
815 if let Some(authority) = path_uri.authority() {
817 url.set_host(Some(authority.host()))
818 .map_err(|_| anyhow!("Failed to set host for request, with path '{path}'"))?;
819 url.set_port(authority.port().map(|p| p.as_u16()))
820 .map_err(|_| anyhow!("Failed to set port for request, with path '{path}'"))?;
821
822 }
824 }
825 }
826
827 if is_absolute_uri(&path_uri) {
837 url.set_path(path_uri.path());
838
839 if url.query().is_some() {
841 url.set_query(None);
842 }
843 } else {
844 let calculated_path = path.split('?').next().unwrap_or(path);
846 url.set_path(calculated_path);
847
848 if let Some(url_query) = url.query() {
850 query_params.add_raw(url_query.to_string());
851 url.set_query(None);
852 }
853 }
854
855 if let Some(path_query) = path_uri.query() {
856 query_params.add_raw(path_query.to_string());
857 }
858
859 Ok(url)
860}
861
862fn is_absolute_uri(path_uri: &Uri) -> bool {
863 path_uri.scheme_str().is_some()
864}
865
866fn has_different_scheme(base_url: &Url, path_uri: &Uri) -> bool {
867 if let Some(scheme) = path_uri.scheme_str() {
868 return scheme != base_url.scheme();
869 }
870
871 false
872}
873
874fn has_different_authority(base_url: &Url, path_uri: &Uri) -> bool {
875 if let Some(authority) = path_uri.authority() {
876 return authority.as_str() != base_url.authority();
877 }
878
879 false
880}
881
882#[cfg(test)]
883mod test_build_url {
884 use super::*;
885
886 #[test]
887 fn it_should_copy_path_to_url_returned_when_restricted() {
888 let base_url = "http://example.com".parse::<Url>().unwrap();
889 let path = "/users";
890 let mut query_params = QueryParamsStore::new();
891 let result = build_url(base_url, &path, &mut query_params, true).unwrap();
892
893 assert_eq!("http://example.com/users", result.as_str());
894 assert!(query_params.is_empty());
895 }
896
897 #[test]
898 fn it_should_copy_all_query_params_to_store_when_restricted() {
899 let base_url = "http://example.com?base=aaa".parse::<Url>().unwrap();
900 let path = "/users?path=bbb&path-flag";
901 let mut query_params = QueryParamsStore::new();
902 let result = build_url(base_url, &path, &mut query_params, true).unwrap();
903
904 assert_eq!("http://example.com/users", result.as_str());
905 assert_eq!("base=aaa&path=bbb&path-flag", query_params.to_string());
906 }
907
908 #[test]
909 fn it_should_not_replace_url_when_restricted_with_different_scheme() {
910 let base_url = "http://example.com?base=666".parse::<Url>().unwrap();
911 let path = "ftp://google.com:123/users.csv?limit=456";
912 let mut query_params = QueryParamsStore::new();
913 let result = build_url(base_url, &path, &mut query_params, true);
914
915 assert!(result.is_err());
916 }
917
918 #[test]
919 fn it_should_not_replace_url_when_restricted_with_same_scheme() {
920 let base_url = "http://example.com?base=666".parse::<Url>().unwrap();
921 let path = "http://google.com:123/users.csv?limit=456";
922 let mut query_params = QueryParamsStore::new();
923 let result = build_url(base_url, &path, &mut query_params, true);
924
925 assert!(result.is_err());
926 }
927
928 #[test]
929 fn it_should_block_url_when_restricted_with_same_scheme() {
930 let base_url = "http://example.com?base=666".parse::<Url>().unwrap();
931 let path = "http://google.com";
932 let mut query_params = QueryParamsStore::new();
933 let result = build_url(base_url, &path, &mut query_params, true);
934
935 assert!(result.is_err());
936 }
937
938 #[test]
939 fn it_should_block_url_when_restricted_and_same_domain_with_different_scheme() {
940 let base_url = "http://example.com?base=666".parse::<Url>().unwrap();
941 let path = "ftp://example.com/users";
942 let mut query_params = QueryParamsStore::new();
943 let result = build_url(base_url, &path, &mut query_params, true);
944
945 assert!(result.is_err());
946 }
947
948 #[test]
949 fn it_should_copy_path_to_url_returned_when_unrestricted() {
950 let base_url = "http://example.com".parse::<Url>().unwrap();
951 let path = "/users";
952 let mut query_params = QueryParamsStore::new();
953 let result = build_url(base_url, &path, &mut query_params, false).unwrap();
954
955 assert_eq!("http://example.com/users", result.as_str());
956 assert!(query_params.is_empty());
957 }
958
959 #[test]
960 fn it_should_copy_all_query_params_to_store_when_unrestricted() {
961 let base_url = "http://example.com?base=aaa".parse::<Url>().unwrap();
962 let path = "/users?path=bbb&path-flag";
963 let mut query_params = QueryParamsStore::new();
964 let result = build_url(base_url, &path, &mut query_params, false).unwrap();
965
966 assert_eq!("http://example.com/users", result.as_str());
967 assert_eq!("base=aaa&path=bbb&path-flag", query_params.to_string());
968 }
969
970 #[test]
971 fn it_should_copy_host_like_a_path_when_unrestricted() {
972 let base_url = "http://example.com".parse::<Url>().unwrap();
973 let path = "google.com";
974 let mut query_params = QueryParamsStore::new();
975 let result = build_url(base_url, &path, &mut query_params, false).unwrap();
976
977 assert_eq!("http://example.com/google.com", result.as_str());
978 assert!(query_params.is_empty());
979 }
980
981 #[test]
982 fn it_should_copy_host_like_a_path_when_restricted() {
983 let base_url = "http://example.com".parse::<Url>().unwrap();
984 let path = "google.com";
985 let mut query_params = QueryParamsStore::new();
986 let result = build_url(base_url, &path, &mut query_params, true).unwrap();
987
988 assert_eq!("http://example.com/google.com", result.as_str());
989 assert!(query_params.is_empty());
990 }
991
992 #[test]
993 fn it_should_replace_url_when_unrestricted() {
994 let base_url = "http://example.com?base=666".parse::<Url>().unwrap();
995 let path = "ftp://google.com:123/users.csv?limit=456";
996 let mut query_params = QueryParamsStore::new();
997 let result = build_url(base_url, &path, &mut query_params, false).unwrap();
998
999 assert_eq!("ftp://google.com:123/users.csv", result.as_str());
1000 assert_eq!("limit=456", query_params.to_string());
1001 }
1002
1003 #[test]
1004 fn it_should_allow_different_scheme_when_unrestricted() {
1005 let base_url = "http://example.com".parse::<Url>().unwrap();
1006 let path = "ftp://example.com";
1007 let mut query_params = QueryParamsStore::new();
1008 let result = build_url(base_url, &path, &mut query_params, false).unwrap();
1009
1010 assert_eq!("ftp://example.com/", result.as_str());
1011 }
1012
1013 #[test]
1014 fn it_should_allow_different_host_when_unrestricted() {
1015 let base_url = "http://example.com".parse::<Url>().unwrap();
1016 let path = "http://google.com";
1017 let mut query_params = QueryParamsStore::new();
1018 let result = build_url(base_url, &path, &mut query_params, false).unwrap();
1019
1020 assert_eq!("http://google.com/", result.as_str());
1021 }
1022
1023 #[test]
1024 fn it_should_allow_different_port_when_unrestricted() {
1025 let base_url = "http://example.com:123".parse::<Url>().unwrap();
1026 let path = "http://example.com:456";
1027 let mut query_params = QueryParamsStore::new();
1028 let result = build_url(base_url, &path, &mut query_params, false).unwrap();
1029
1030 assert_eq!("http://example.com:456/", result.as_str());
1031 }
1032
1033 #[test]
1034 fn it_should_allow_same_host_port_when_unrestricted() {
1035 let base_url = "http://example.com:123".parse::<Url>().unwrap();
1036 let path = "http://example.com:123";
1037 let mut query_params = QueryParamsStore::new();
1038 let result = build_url(base_url, &path, &mut query_params, false).unwrap();
1039
1040 assert_eq!("http://example.com:123/", result.as_str());
1041 }
1042
1043 #[test]
1044 fn it_should_not_allow_different_scheme_when_restricted() {
1045 let base_url = "http://example.com".parse::<Url>().unwrap();
1046 let path = "ftp://example.com";
1047 let mut query_params = QueryParamsStore::new();
1048 let result = build_url(base_url, &path, &mut query_params, true);
1049
1050 assert!(result.is_err());
1051 }
1052
1053 #[test]
1054 fn it_should_not_allow_different_host_when_restricted() {
1055 let base_url = "http://example.com".parse::<Url>().unwrap();
1056 let path = "http://google.com";
1057 let mut query_params = QueryParamsStore::new();
1058 let result = build_url(base_url, &path, &mut query_params, true);
1059
1060 assert!(result.is_err());
1061 }
1062
1063 #[test]
1064 fn it_should_not_allow_different_port_when_restricted() {
1065 let base_url = "http://example.com:123".parse::<Url>().unwrap();
1066 let path = "http://example.com:456";
1067 let mut query_params = QueryParamsStore::new();
1068 let result = build_url(base_url, &path, &mut query_params, true);
1069
1070 assert!(result.is_err());
1071 }
1072
1073 #[test]
1074 fn it_should_allow_same_host_port_when_restricted() {
1075 let base_url = "http://example.com:123".parse::<Url>().unwrap();
1076 let path = "http://example.com:123";
1077 let mut query_params = QueryParamsStore::new();
1078 let result = build_url(base_url, &path, &mut query_params, true).unwrap();
1079
1080 assert_eq!("http://example.com:123/", result.as_str());
1081 }
1082}
1083
1084#[cfg(test)]
1085mod test_new {
1086 use axum::Router;
1087 use axum::routing::get;
1088 use std::net::SocketAddr;
1089
1090 use crate::TestServer;
1091
1092 async fn get_ping() -> &'static str {
1093 "pong!"
1094 }
1095
1096 #[tokio::test]
1097 async fn it_should_run_into_make_into_service_with_connect_info_by_default() {
1098 let app = Router::new()
1100 .route("/ping", get(get_ping))
1101 .into_make_service_with_connect_info::<SocketAddr>();
1102
1103 let server = TestServer::new(app);
1105
1106 server.get(&"/ping").await.assert_text(&"pong!");
1108 }
1109}
1110
1111#[cfg(test)]
1112mod test_get {
1113 use super::*;
1114 use crate::testing::catch_panic_error_message;
1115 use axum::Router;
1116 use axum::routing::get;
1117 use pretty_assertions::assert_str_eq;
1118 use reserve_port::ReservedSocketAddr;
1119
1120 async fn get_ping() -> &'static str {
1121 "pong!"
1122 }
1123
1124 #[tokio::test]
1125 async fn it_should_get_using_relative_path_with_slash() {
1126 let app = Router::new().route("/ping", get(get_ping));
1127 let server = TestServer::new(app);
1128
1129 server.get(&"/ping").await.assert_text(&"pong!");
1131 }
1132
1133 #[tokio::test]
1134 async fn it_should_get_using_relative_path_without_slash() {
1135 let app = Router::new().route("/ping", get(get_ping));
1136 let server = TestServer::new(app);
1137
1138 server.get(&"ping").await.assert_text(&"pong!");
1140 }
1141
1142 #[tokio::test]
1143 async fn it_should_get_using_absolute_path() {
1144 let app = Router::new().route("/ping", get(get_ping));
1146
1147 let reserved_address = ReservedSocketAddr::reserve_random_socket_addr().unwrap();
1149 let ip = reserved_address.ip();
1150 let port = reserved_address.port();
1151
1152 let server = TestServer::builder()
1154 .http_transport_with_ip_port(Some(ip), Some(port))
1155 .try_build(app)
1156 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1157
1158 let absolute_url = format!("http://{ip}:{port}/ping");
1160 let response = server.get(&absolute_url).await;
1161
1162 response.assert_text(&"pong!");
1163 let request_path = response.request_url();
1164 assert_eq!(request_path.to_string(), format!("http://{ip}:{port}/ping"));
1165 }
1166
1167 #[tokio::test]
1168 async fn it_should_get_using_absolute_path_and_restricted_if_path_is_for_server() {
1169 let app = Router::new().route("/ping", get(get_ping));
1171
1172 let reserved_address = ReservedSocketAddr::reserve_random_socket_addr().unwrap();
1174 let ip = reserved_address.ip();
1175 let port = reserved_address.port();
1176
1177 let server = TestServer::builder()
1179 .http_transport_with_ip_port(Some(ip), Some(port))
1180 .restrict_requests_with_http_scheme() .try_build(app)
1182 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1183
1184 let absolute_url = format!("http://{ip}:{port}/ping");
1186 let response = server.get(&absolute_url).await;
1187
1188 response.assert_text(&"pong!");
1189 let request_path = response.request_url();
1190 assert_eq!(request_path.to_string(), format!("http://{ip}:{port}/ping"));
1191 }
1192
1193 #[tokio::test]
1194 async fn it_should_not_get_using_absolute_path_if_restricted_and_different_port() {
1195 let app = Router::new().route("/ping", get(get_ping));
1197
1198 let reserved_address = ReservedSocketAddr::reserve_random_socket_addr().unwrap();
1200 let ip = reserved_address.ip();
1201 let mut port = reserved_address.port();
1202
1203 let server = TestServer::builder()
1205 .http_transport_with_ip_port(Some(ip), Some(port))
1206 .restrict_requests_with_http_scheme() .try_build(app)
1208 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1209
1210 port += 1; let absolute_url = format!("http://{ip}:{port}/ping");
1213
1214 let message = catch_panic_error_message(|| {
1215 let _ = server.get(&absolute_url);
1216 });
1217
1218 let expected = format!("Failed to build request, for GET http://{ip}:{port}/ping,
1219 Request disallowed for path 'http://{ip}:{port}/ping', requests are only allowed to local server. Turn off 'restrict_requests_with_http_scheme' to change this.
1220");
1221 assert_str_eq!(expected, message);
1222 }
1223
1224 #[tokio::test]
1225 async fn it_should_work_in_parallel() {
1226 let app = Router::new().route("/ping", get(get_ping));
1227 let server = TestServer::new(app);
1228
1229 let future1 = async { server.get("/ping").await };
1230 let future2 = async { server.get("/ping").await };
1231 let (r1, r2) = tokio::join!(future1, future2);
1232
1233 assert_eq!(r1.text(), r2.text());
1234 }
1235
1236 #[tokio::test]
1237 async fn it_should_work_in_parallel_with_sleeping_requests() {
1238 let app = axum::Router::new().route(
1239 &"/slow",
1240 axum::routing::get(|| async {
1241 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
1242 "hello!"
1243 }),
1244 );
1245
1246 let server = TestServer::new(app);
1247
1248 let future1 = async { server.get("/slow").await };
1249 let future2 = async { server.get("/slow").await };
1250 let (r1, r2) = tokio::join!(future1, future2);
1251
1252 assert_eq!(r1.text(), r2.text());
1253 }
1254}
1255
1256#[cfg(feature = "reqwest")]
1257#[cfg(test)]
1258mod test_reqwest_get {
1259 use super::*;
1260 use axum::Router;
1261 use axum::routing::get;
1262
1263 async fn get_ping() -> &'static str {
1264 "pong!"
1265 }
1266
1267 #[tokio::test]
1268 async fn it_should_get_using_relative_path_with_slash() {
1269 let app = Router::new().route("/ping", get(get_ping));
1270 let server = TestServer::builder().http_transport().build(app);
1271
1272 let response = server
1273 .reqwest_get(&"/ping")
1274 .send()
1275 .await
1276 .unwrap()
1277 .text()
1278 .await
1279 .unwrap();
1280
1281 assert_eq!(response, "pong!");
1282 }
1283}
1284
1285#[cfg(feature = "reqwest")]
1286#[cfg(test)]
1287mod test_reqwest_post {
1288 use super::*;
1289 use axum::Json;
1290 use axum::Router;
1291 use axum::routing::post;
1292 use serde::Deserialize;
1293
1294 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1295 struct TestBody {
1296 number: u32,
1297 text: String,
1298 }
1299
1300 async fn post_json(Json(body): Json<TestBody>) -> Json<TestBody> {
1301 let response = TestBody {
1302 number: body.number * 2,
1303 text: format!("{}_plus_response", body.text),
1304 };
1305
1306 Json(response)
1307 }
1308
1309 #[tokio::test]
1310 async fn it_should_post_and_receive_json() {
1311 let app = Router::new().route("/json", post(post_json));
1312 let server = TestServer::builder().http_transport().build(app);
1313
1314 let response = server
1315 .reqwest_post(&"/json")
1316 .json(&TestBody {
1317 number: 111,
1318 text: format!("request"),
1319 })
1320 .send()
1321 .await
1322 .unwrap()
1323 .json::<TestBody>()
1324 .await
1325 .unwrap();
1326
1327 assert_eq!(
1328 response,
1329 TestBody {
1330 number: 222,
1331 text: format!("request_plus_response"),
1332 }
1333 );
1334 }
1335}
1336
1337#[cfg(test)]
1338mod test_server_address {
1339 use super::*;
1340 use axum::Router;
1341 use regex::Regex;
1342 use reserve_port::ReservedPort;
1343 use std::net::Ipv4Addr;
1344
1345 #[tokio::test]
1346 async fn it_should_return_address_used_from_config() {
1347 let reserved_port = ReservedPort::random().unwrap();
1348 let ip = Ipv4Addr::LOCALHOST.into();
1349 let port = reserved_port.port();
1350
1351 let app = Router::new();
1353 let server = TestServer::builder()
1354 .http_transport_with_ip_port(Some(ip), Some(port))
1355 .try_build(app)
1356 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1357
1358 let expected_ip_port = format!("http://{}:{}/", ip, reserved_port.port());
1359 assert_eq!(
1360 server.server_address().unwrap().to_string(),
1361 expected_ip_port
1362 );
1363 }
1364
1365 #[tokio::test]
1366 async fn it_should_return_default_address_without_ending_slash() {
1367 let app = Router::new();
1368 let server = TestServer::builder().http_transport().build(app);
1369
1370 let address_regex = Regex::new("^http://127\\.0\\.0\\.1:[0-9]+/$").unwrap();
1371 let is_match = address_regex.is_match(&server.server_address().unwrap().to_string());
1372 assert!(is_match);
1373 }
1374
1375 #[tokio::test]
1376 async fn it_should_return_none_on_mock_transport() {
1377 let app = Router::new();
1378 let server = TestServer::builder().mock_transport().build(app);
1379
1380 assert!(server.server_address().is_none());
1381 }
1382}
1383
1384#[cfg(test)]
1385mod test_server_url {
1386 use super::*;
1387 use axum::Router;
1388 use pretty_assertions::assert_str_eq;
1389 use regex::Regex;
1390 use reserve_port::ReservedPort;
1391 use std::net::Ipv4Addr;
1392
1393 #[tokio::test]
1394 async fn it_should_return_address_with_url_on_http_ip_port() {
1395 let reserved_port = ReservedPort::random().unwrap();
1396 let ip = Ipv4Addr::LOCALHOST.into();
1397 let port = reserved_port.port();
1398
1399 let app = Router::new();
1401 let server = TestServer::builder()
1402 .http_transport_with_ip_port(Some(ip), Some(port))
1403 .try_build(app)
1404 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1405
1406 let expected_ip_port_url = format!("http://{}:{}/users", ip, reserved_port.port());
1407 let absolute_url = server.server_url("/users").unwrap().to_string();
1408 assert_eq!(expected_ip_port_url, absolute_url);
1409 }
1410
1411 #[tokio::test]
1412 async fn it_should_return_address_with_url_on_random_http() {
1413 let app = Router::new();
1414 let server = TestServer::builder().http_transport().build(app);
1415
1416 let address_regex =
1417 Regex::new("^http://127\\.0\\.0\\.1:[0-9]+/users/123\\?filter=enabled$").unwrap();
1418 let absolute_url = &server
1419 .server_url(&"/users/123?filter=enabled")
1420 .unwrap()
1421 .to_string();
1422
1423 let is_match = address_regex.is_match(absolute_url);
1424 assert!(is_match);
1425 }
1426
1427 #[tokio::test]
1428 async fn it_should_error_on_mock_transport() {
1429 let app = Router::new();
1431 let server = TestServer::builder().mock_transport().build(app);
1432
1433 let result = server.server_url("/users");
1434 assert!(result.is_err());
1435 }
1436
1437 #[tokio::test]
1438 async fn it_should_include_path_query_params() {
1439 let reserved_port = ReservedPort::random().unwrap();
1440 let ip = Ipv4Addr::LOCALHOST.into();
1441 let port = reserved_port.port();
1442
1443 let app = Router::new();
1445 let server = TestServer::builder()
1446 .http_transport_with_ip_port(Some(ip), Some(port))
1447 .try_build(app)
1448 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1449
1450 let expected_url = format!(
1451 "http://{}:{}/users?filter=enabled",
1452 ip,
1453 reserved_port.port()
1454 );
1455 let received_url = server
1456 .server_url("/users?filter=enabled")
1457 .unwrap()
1458 .to_string();
1459
1460 assert_eq!(expected_url, received_url);
1461 }
1462
1463 #[tokio::test]
1464 async fn it_should_include_server_query_params() {
1465 let reserved_port = ReservedPort::random().unwrap();
1466 let ip = Ipv4Addr::LOCALHOST.into();
1467 let port = reserved_port.port();
1468
1469 let app = Router::new();
1471 let mut server = TestServer::builder()
1472 .http_transport_with_ip_port(Some(ip), Some(port))
1473 .try_build(app)
1474 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1475
1476 server.add_query_param("filter", "enabled");
1477
1478 let expected_url = format!(
1479 "http://{}:{}/users?filter=enabled",
1480 ip,
1481 reserved_port.port()
1482 );
1483 let received_url = server.server_url("/users").unwrap().to_string();
1484
1485 assert_eq!(expected_url, received_url);
1486 }
1487
1488 #[tokio::test]
1489 async fn it_should_include_server_and_path_query_params() {
1490 let reserved_port = ReservedPort::random().unwrap();
1491 let ip = Ipv4Addr::LOCALHOST.into();
1492 let port = reserved_port.port();
1493
1494 let app = Router::new();
1496 let mut server = TestServer::builder()
1497 .http_transport_with_ip_port(Some(ip), Some(port))
1498 .try_build(app)
1499 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1500
1501 server.add_query_param("filter", "enabled");
1502
1503 let expected_url = format!(
1504 "http://{}:{}/users?filter=enabled&animal=donkeys",
1505 ip,
1506 reserved_port.port()
1507 );
1508 let received_url = server
1509 .server_url("/users?animal=donkeys")
1510 .unwrap()
1511 .to_string();
1512
1513 assert_eq!(expected_url, received_url);
1514 }
1515
1516 #[tokio::test]
1517 async fn it_should_include_both_server_and_path_queries() {
1518 let reserved_port = ReservedPort::random().unwrap();
1519 let ip = Ipv4Addr::LOCALHOST.into();
1520 let port = reserved_port.port();
1521
1522 let app = Router::new();
1524 let mut server = TestServer::builder()
1525 .http_transport_with_ip_port(Some(ip), Some(port))
1526 .try_build(app)
1527 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1528
1529 server.add_query_param("query", "server");
1530
1531 let expected_url = format!(
1532 "http://{}:{}/users?query=server&query=path",
1533 ip,
1534 reserved_port.port()
1535 );
1536 let received_url = server.server_url("/users?query=path").unwrap().to_string();
1537
1538 assert_eq!(expected_url, received_url);
1539 }
1540
1541 #[tokio::test]
1542 async fn it_should_work_for_paths_with_leading_slash() {
1543 let reserved_port = ReservedPort::random().unwrap();
1544 let ip = Ipv4Addr::LOCALHOST.into();
1545 let port = reserved_port.port();
1546
1547 let app = Router::new();
1549 let server = TestServer::builder()
1550 .http_transport_with_ip_port(Some(ip), Some(port))
1551 .try_build(app)
1552 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1553
1554 let expected_url = format!("http://{}:{}/users", ip, reserved_port.port());
1555 let received_url = server.server_url("users").unwrap().to_string();
1556
1557 assert_eq!(expected_url, received_url);
1558 }
1559
1560 #[tokio::test]
1562 async fn it_should_panic_when_provided_an_empty_path() {
1563 let reserved_port = ReservedPort::random().unwrap();
1564 let ip = Ipv4Addr::LOCALHOST.into();
1565 let port = reserved_port.port();
1566
1567 let app = Router::new();
1569 let server = TestServer::builder()
1570 .http_transport_with_ip_port(Some(ip), Some(port))
1571 .try_build(app)
1572 .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1573
1574 let error_message = server.server_url("").unwrap_err().to_string();
1576
1577 assert_str_eq!("empty string", error_message);
1578 }
1579}
1580
1581#[cfg(test)]
1582mod test_add_cookie {
1583 use crate::TestServer;
1584 use axum::Router;
1585 use axum::routing::get;
1586 use axum_extra::extract::cookie::CookieJar;
1587 use cookie::Cookie;
1588
1589 const TEST_COOKIE_NAME: &'static str = &"test-cookie";
1590
1591 async fn get_cookie(cookies: CookieJar) -> (CookieJar, String) {
1592 let cookie = cookies.get(&TEST_COOKIE_NAME);
1593 let cookie_value = cookie
1594 .map(|c| c.value().to_string())
1595 .unwrap_or_else(|| "cookie-not-found".to_string());
1596
1597 (cookies, cookie_value)
1598 }
1599
1600 #[tokio::test]
1601 async fn it_should_send_cookies_added_to_request() {
1602 let app = Router::new().route("/cookie", get(get_cookie));
1603 let mut server = TestServer::new(app);
1604
1605 let cookie = Cookie::new(TEST_COOKIE_NAME, "my-custom-cookie");
1606 server.add_cookie(cookie);
1607
1608 let response_text = server.get(&"/cookie").await.text();
1609 assert_eq!(response_text, "my-custom-cookie");
1610 }
1611}
1612
1613#[cfg(test)]
1614mod test_add_cookies {
1615 use crate::TestServer;
1616
1617 use axum::Router;
1618 use axum::routing::get;
1619 use axum_extra::extract::cookie::CookieJar as AxumCookieJar;
1620 use cookie::Cookie;
1621 use cookie::CookieJar;
1622
1623 async fn route_get_cookies(cookies: AxumCookieJar) -> String {
1624 let mut all_cookies = cookies
1625 .iter()
1626 .map(|cookie| format!("{}={}", cookie.name(), cookie.value()))
1627 .collect::<Vec<String>>();
1628 all_cookies.sort();
1629
1630 all_cookies.join(&", ")
1631 }
1632
1633 #[tokio::test]
1634 async fn it_should_send_all_cookies_added_by_jar() {
1635 let app = Router::new().route("/cookies", get(route_get_cookies));
1636 let mut server = TestServer::new(app);
1637
1638 let cookie_1 = Cookie::new("first-cookie", "my-custom-cookie");
1640 let cookie_2 = Cookie::new("second-cookie", "other-cookie");
1641 let mut cookie_jar = CookieJar::new();
1642 cookie_jar.add(cookie_1);
1643 cookie_jar.add(cookie_2);
1644
1645 server.add_cookies(cookie_jar);
1646
1647 server
1648 .get(&"/cookies")
1649 .await
1650 .assert_text("first-cookie=my-custom-cookie, second-cookie=other-cookie");
1651 }
1652}
1653
1654#[cfg(test)]
1655mod test_clear_cookies {
1656 use crate::TestServer;
1657
1658 use axum::Router;
1659 use axum::routing::get;
1660 use axum_extra::extract::cookie::CookieJar as AxumCookieJar;
1661 use cookie::Cookie;
1662 use cookie::CookieJar;
1663
1664 async fn route_get_cookies(cookies: AxumCookieJar) -> String {
1665 let mut all_cookies = cookies
1666 .iter()
1667 .map(|cookie| format!("{}={}", cookie.name(), cookie.value()))
1668 .collect::<Vec<String>>();
1669 all_cookies.sort();
1670
1671 all_cookies.join(&", ")
1672 }
1673
1674 #[tokio::test]
1675 async fn it_should_not_send_cookies_cleared() {
1676 let app = Router::new().route("/cookies", get(route_get_cookies));
1677 let mut server = TestServer::new(app);
1678
1679 let cookie_1 = Cookie::new("first-cookie", "my-custom-cookie");
1680 let cookie_2 = Cookie::new("second-cookie", "other-cookie");
1681 let mut cookie_jar = CookieJar::new();
1682 cookie_jar.add(cookie_1);
1683 cookie_jar.add(cookie_2);
1684
1685 server.add_cookies(cookie_jar);
1686
1687 server.clear_cookies();
1689
1690 server.get(&"/cookies").await.assert_text("");
1691 }
1692}
1693
1694#[cfg(test)]
1695mod test_add_header {
1696 use super::*;
1697 use crate::TestServer;
1698 use axum::Router;
1699 use axum::extract::FromRequestParts;
1700 use axum::routing::get;
1701 use http::HeaderName;
1702 use http::HeaderValue;
1703 use http::request::Parts;
1704 use hyper::StatusCode;
1705 use std::marker::Sync;
1706
1707 const TEST_HEADER_NAME: &'static str = &"test-header";
1708 const TEST_HEADER_CONTENT: &'static str = &"Test header content";
1709
1710 struct TestHeader(Vec<u8>);
1711
1712 impl<S: Sync> FromRequestParts<S> for TestHeader {
1713 type Rejection = (StatusCode, &'static str);
1714
1715 async fn from_request_parts(
1716 parts: &mut Parts,
1717 _state: &S,
1718 ) -> Result<TestHeader, Self::Rejection> {
1719 parts
1720 .headers
1721 .get(HeaderName::from_static(TEST_HEADER_NAME))
1722 .map(|v| TestHeader(v.as_bytes().to_vec()))
1723 .ok_or((StatusCode::BAD_REQUEST, "Missing test header"))
1724 }
1725 }
1726
1727 async fn ping_header(TestHeader(header): TestHeader) -> Vec<u8> {
1728 header
1729 }
1730
1731 #[tokio::test]
1732 async fn it_should_send_header_added_to_server() {
1733 let app = Router::new().route("/header", get(ping_header));
1735
1736 let mut server = TestServer::new(app);
1738 server.add_header(
1739 HeaderName::from_static(TEST_HEADER_NAME),
1740 HeaderValue::from_static(TEST_HEADER_CONTENT),
1741 );
1742
1743 let response = server.get(&"/header").await;
1745
1746 response.assert_text(TEST_HEADER_CONTENT);
1748 }
1749}
1750
1751#[cfg(test)]
1752mod test_clear_headers {
1753 use super::*;
1754 use crate::TestServer;
1755 use axum::Router;
1756 use axum::extract::FromRequestParts;
1757 use axum::routing::get;
1758 use http::HeaderName;
1759 use http::HeaderValue;
1760 use http::request::Parts;
1761 use hyper::StatusCode;
1762 use std::marker::Sync;
1763
1764 const TEST_HEADER_NAME: &'static str = &"test-header";
1765 const TEST_HEADER_CONTENT: &'static str = &"Test header content";
1766
1767 struct TestHeader(Vec<u8>);
1768
1769 impl<S: Sync> FromRequestParts<S> for TestHeader {
1770 type Rejection = (StatusCode, &'static str);
1771
1772 async fn from_request_parts(
1773 parts: &mut Parts,
1774 _state: &S,
1775 ) -> Result<Self, Self::Rejection> {
1776 parts
1777 .headers
1778 .get(HeaderName::from_static(TEST_HEADER_NAME))
1779 .map(|v| TestHeader(v.as_bytes().to_vec()))
1780 .ok_or((StatusCode::BAD_REQUEST, "Missing test header"))
1781 }
1782 }
1783
1784 async fn ping_header(TestHeader(header): TestHeader) -> Vec<u8> {
1785 header
1786 }
1787
1788 #[tokio::test]
1789 async fn it_should_not_send_headers_cleared_by_server() {
1790 let app = Router::new().route("/header", get(ping_header));
1792
1793 let mut server = TestServer::new(app);
1795 server.add_header(
1796 HeaderName::from_static(TEST_HEADER_NAME),
1797 HeaderValue::from_static(TEST_HEADER_CONTENT),
1798 );
1799 server.clear_headers();
1800
1801 let response = server.get(&"/header").await;
1803
1804 response.assert_status_bad_request();
1806 response.assert_text("Missing test header");
1807 }
1808}
1809
1810#[cfg(test)]
1811mod test_add_query_params {
1812 use axum::Router;
1813 use axum::extract::Query;
1814 use axum::routing::get;
1815
1816 use serde::Deserialize;
1817 use serde::Serialize;
1818 use serde_json::json;
1819
1820 use crate::TestServer;
1821
1822 #[derive(Debug, Deserialize, Serialize)]
1823 struct QueryParam {
1824 message: String,
1825 }
1826
1827 async fn get_query_param(Query(params): Query<QueryParam>) -> String {
1828 params.message
1829 }
1830
1831 #[derive(Debug, Deserialize, Serialize)]
1832 struct QueryParam2 {
1833 message: String,
1834 other: String,
1835 }
1836
1837 async fn get_query_param_2(Query(params): Query<QueryParam2>) -> String {
1838 format!("{}-{}", params.message, params.other)
1839 }
1840
1841 #[tokio::test]
1842 async fn it_should_pass_up_query_params_from_serialization() {
1843 let app = Router::new().route("/query", get(get_query_param));
1845
1846 let mut server = TestServer::new(app);
1848 server.add_query_params(QueryParam {
1849 message: "it works".to_string(),
1850 });
1851
1852 server.get(&"/query").await.assert_text(&"it works");
1854 }
1855
1856 #[tokio::test]
1857 async fn it_should_pass_up_query_params_from_pairs() {
1858 let app = Router::new().route("/query", get(get_query_param));
1860
1861 let mut server = TestServer::new(app);
1863 server.add_query_params(&[("message", "it works")]);
1864
1865 server.get(&"/query").await.assert_text(&"it works");
1867 }
1868
1869 #[tokio::test]
1870 async fn it_should_pass_up_multiple_query_params_from_multiple_params() {
1871 let app = Router::new().route("/query-2", get(get_query_param_2));
1873
1874 let mut server = TestServer::new(app);
1876 server.add_query_params(&[("message", "it works"), ("other", "yup")]);
1877
1878 server.get(&"/query-2").await.assert_text(&"it works-yup");
1880 }
1881
1882 #[tokio::test]
1883 async fn it_should_pass_up_multiple_query_params_from_multiple_calls() {
1884 let app = Router::new().route("/query-2", get(get_query_param_2));
1886
1887 let mut server = TestServer::new(app);
1889 server.add_query_params(&[("message", "it works")]);
1890 server.add_query_params(&[("other", "yup")]);
1891
1892 server.get(&"/query-2").await.assert_text(&"it works-yup");
1894 }
1895
1896 #[tokio::test]
1897 async fn it_should_pass_up_multiple_query_params_from_json() {
1898 let app = Router::new().route("/query-2", get(get_query_param_2));
1900
1901 let mut server = TestServer::new(app);
1903 server.add_query_params(json!({
1904 "message": "it works",
1905 "other": "yup"
1906 }));
1907
1908 server.get(&"/query-2").await.assert_text(&"it works-yup");
1910 }
1911}
1912
1913#[cfg(test)]
1914mod test_add_query_param {
1915 use axum::Router;
1916 use axum::extract::Query;
1917 use axum::routing::get;
1918
1919 use serde::Deserialize;
1920 use serde::Serialize;
1921
1922 use crate::TestServer;
1923
1924 #[derive(Debug, Deserialize, Serialize)]
1925 struct QueryParam {
1926 message: String,
1927 }
1928
1929 async fn get_query_param(Query(params): Query<QueryParam>) -> String {
1930 params.message
1931 }
1932
1933 #[derive(Debug, Deserialize, Serialize)]
1934 struct QueryParam2 {
1935 message: String,
1936 other: String,
1937 }
1938
1939 async fn get_query_param_2(Query(params): Query<QueryParam2>) -> String {
1940 format!("{}-{}", params.message, params.other)
1941 }
1942
1943 #[tokio::test]
1944 async fn it_should_pass_up_query_params_from_pairs() {
1945 let app = Router::new().route("/query", get(get_query_param));
1947
1948 let mut server = TestServer::new(app);
1950 server.add_query_param("message", "it works");
1951
1952 server.get(&"/query").await.assert_text(&"it works");
1954 }
1955
1956 #[tokio::test]
1957 async fn it_should_pass_up_multiple_query_params_from_multiple_calls() {
1958 let app = Router::new().route("/query-2", get(get_query_param_2));
1960
1961 let mut server = TestServer::new(app);
1963 server.add_query_param("message", "it works");
1964 server.add_query_param("other", "yup");
1965
1966 server.get(&"/query-2").await.assert_text(&"it works-yup");
1968 }
1969
1970 #[tokio::test]
1971 async fn it_should_pass_up_multiple_query_params_from_calls_across_server_and_request() {
1972 let app = Router::new().route("/query-2", get(get_query_param_2));
1974
1975 let mut server = TestServer::new(app);
1977 server.add_query_param("message", "it works");
1978
1979 server
1981 .get(&"/query-2")
1982 .add_query_param("other", "yup")
1983 .await
1984 .assert_text(&"it works-yup");
1985 }
1986}
1987
1988#[cfg(test)]
1989mod test_add_raw_query_param {
1990 use axum::Router;
1991 use axum::extract::Query as AxumStdQuery;
1992 use axum::routing::get;
1993 use axum_extra::extract::Query as AxumExtraQuery;
1994 use serde::Deserialize;
1995 use serde::Serialize;
1996 use std::fmt::Write;
1997
1998 use crate::TestServer;
1999
2000 #[derive(Debug, Deserialize, Serialize)]
2001 struct QueryParam {
2002 message: String,
2003 }
2004
2005 async fn get_query_param(AxumStdQuery(params): AxumStdQuery<QueryParam>) -> String {
2006 params.message
2007 }
2008
2009 #[derive(Debug, Deserialize, Serialize)]
2010 struct QueryParamExtra {
2011 #[serde(default)]
2012 items: Vec<String>,
2013
2014 #[serde(default, rename = "arrs[]")]
2015 arrs: Vec<String>,
2016 }
2017
2018 async fn get_query_param_extra(
2019 AxumExtraQuery(params): AxumExtraQuery<QueryParamExtra>,
2020 ) -> String {
2021 let mut output = String::new();
2022
2023 if params.items.len() > 0 {
2024 write!(output, "{}", params.items.join(", ")).unwrap();
2025 }
2026
2027 if params.arrs.len() > 0 {
2028 write!(output, "{}", params.arrs.join(", ")).unwrap();
2029 }
2030
2031 output
2032 }
2033
2034 fn build_app() -> Router {
2035 Router::new()
2036 .route("/query", get(get_query_param))
2037 .route("/query-extra", get(get_query_param_extra))
2038 }
2039
2040 #[tokio::test]
2041 async fn it_should_pass_up_query_param_as_is() {
2042 let mut server = TestServer::new(build_app());
2044 server.add_raw_query_param(&"message=it-works");
2045
2046 server.get(&"/query").await.assert_text(&"it-works");
2048 }
2049
2050 #[tokio::test]
2051 async fn it_should_pass_up_array_query_params_as_one_string() {
2052 let mut server = TestServer::new(build_app());
2054 server.add_raw_query_param(&"items=one&items=two&items=three");
2055
2056 server
2058 .get(&"/query-extra")
2059 .await
2060 .assert_text(&"one, two, three");
2061 }
2062
2063 #[tokio::test]
2064 async fn it_should_pass_up_array_query_params_as_multiple_params() {
2065 let mut server = TestServer::new(build_app());
2067 server.add_raw_query_param(&"arrs[]=one");
2068 server.add_raw_query_param(&"arrs[]=two");
2069 server.add_raw_query_param(&"arrs[]=three");
2070
2071 server
2073 .get(&"/query-extra")
2074 .await
2075 .assert_text(&"one, two, three");
2076 }
2077}
2078
2079#[cfg(test)]
2080mod test_clear_query_params {
2081 use axum::Router;
2082 use axum::extract::Query;
2083 use axum::routing::get;
2084
2085 use serde::Deserialize;
2086 use serde::Serialize;
2087
2088 use crate::TestServer;
2089
2090 #[derive(Debug, Deserialize, Serialize)]
2091 struct QueryParams {
2092 first: Option<String>,
2093 second: Option<String>,
2094 }
2095
2096 async fn get_query_params(Query(params): Query<QueryParams>) -> String {
2097 format!(
2098 "has first? {}, has second? {}",
2099 params.first.is_some(),
2100 params.second.is_some()
2101 )
2102 }
2103
2104 #[tokio::test]
2105 async fn it_should_clear_all_params_set() {
2106 let app = Router::new().route("/query", get(get_query_params));
2108
2109 let mut server = TestServer::new(app);
2111 server.add_query_params(QueryParams {
2112 first: Some("first".to_string()),
2113 second: Some("second".to_string()),
2114 });
2115 server.clear_query_params();
2116
2117 server
2119 .get(&"/query")
2120 .await
2121 .assert_text(&"has first? false, has second? false");
2122 }
2123
2124 #[tokio::test]
2125 async fn it_should_clear_all_params_set_and_allow_replacement() {
2126 let app = Router::new().route("/query", get(get_query_params));
2128
2129 let mut server = TestServer::new(app);
2131 server.add_query_params(QueryParams {
2132 first: Some("first".to_string()),
2133 second: Some("second".to_string()),
2134 });
2135 server.clear_query_params();
2136 server.add_query_params(QueryParams {
2137 first: Some("first".to_string()),
2138 second: Some("second".to_string()),
2139 });
2140
2141 server
2143 .get(&"/query")
2144 .await
2145 .assert_text(&"has first? true, has second? true");
2146 }
2147}
2148
2149#[cfg(test)]
2150mod test_expect_success_by_default {
2151 use super::*;
2152 use crate::testing::catch_panic_error_message_async;
2153 use axum::Router;
2154 use axum::routing::get;
2155 use pretty_assertions::assert_str_eq;
2156
2157 #[tokio::test]
2158 async fn it_should_not_panic_by_default_if_accessing_404_route() {
2159 let app = Router::new();
2160 let server = TestServer::new(app);
2161
2162 server.get(&"/some_unknown_route").await;
2163 }
2164
2165 #[tokio::test]
2166 async fn it_should_not_panic_by_default_if_accessing_200_route() {
2167 let app = Router::new().route("/known_route", get(|| async { "🦊🦊🦊" }));
2168 let server = TestServer::new(app);
2169
2170 server.get(&"/known_route").await;
2171 }
2172
2173 #[tokio::test]
2174 async fn it_should_panic_by_default_if_accessing_404_route_and_expect_success_on() {
2175 let app = Router::new();
2176 let server = TestServer::builder().expect_success_by_default().build(app);
2177
2178 let message = catch_panic_error_message_async(server.get(&"/some_unknown_route")).await;
2179 assert_str_eq!(
2180 "Expect status code within 2xx range, received 404 (Not Found), for request GET http://localhost/some_unknown_route, with body ''",
2181 message
2182 );
2183 }
2184
2185 #[tokio::test]
2186 async fn it_should_not_panic_by_default_if_accessing_200_route_and_expect_success_on() {
2187 let app = Router::new().route("/known_route", get(|| async { "🦊🦊🦊" }));
2188 let server = TestServer::builder().expect_success_by_default().build(app);
2189
2190 server.get(&"/known_route").await;
2191 }
2192}
2193
2194#[cfg(test)]
2195mod test_content_type {
2196 use super::*;
2197 use axum::Router;
2198 use axum::routing::get;
2199 use http::HeaderMap;
2200 use http::header::CONTENT_TYPE;
2201
2202 async fn get_content_type(headers: HeaderMap) -> String {
2203 headers
2204 .get(CONTENT_TYPE)
2205 .map(|h| h.to_str().unwrap().to_string())
2206 .unwrap_or_else(|| "".to_string())
2207 }
2208
2209 #[tokio::test]
2210 async fn it_should_default_to_server_content_type_when_present() {
2211 let app = Router::new().route("/content_type", get(get_content_type));
2213
2214 let server = TestServer::builder()
2216 .default_content_type("text/plain")
2217 .build(app);
2218
2219 let text = server.get(&"/content_type").await.text();
2221
2222 assert_eq!(text, "text/plain");
2223 }
2224}
2225
2226#[cfg(test)]
2227mod test_expect_success {
2228 use crate::TestServer;
2229 use crate::testing::catch_panic_error_message_async;
2230 use axum::Router;
2231 use axum::routing::get;
2232 use http::StatusCode;
2233 use pretty_assertions::assert_str_eq;
2234
2235 #[tokio::test]
2236 async fn it_should_not_panic_if_success_is_returned() {
2237 async fn get_ping() -> &'static str {
2238 "pong!"
2239 }
2240
2241 let app = Router::new().route("/ping", get(get_ping));
2243
2244 let mut server = TestServer::new(app);
2246 server.expect_success();
2247
2248 server.get(&"/ping").await;
2250 }
2251
2252 #[tokio::test]
2253 async fn it_should_not_panic_on_other_2xx_status_code() {
2254 async fn get_accepted() -> StatusCode {
2255 StatusCode::ACCEPTED
2256 }
2257
2258 let app = Router::new().route("/accepted", get(get_accepted));
2260
2261 let mut server = TestServer::new(app);
2263 server.expect_success();
2264
2265 server.get(&"/accepted").await;
2267 }
2268
2269 #[tokio::test]
2270 async fn it_should_panic_on_404() {
2271 let app = Router::new();
2273
2274 let mut server = TestServer::new(app);
2276 server.expect_success();
2277
2278 let message = catch_panic_error_message_async(server.get(&"/some_unknown_route")).await;
2280 assert_str_eq!(
2281 "Expect status code within 2xx range, received 404 (Not Found), for request GET http://localhost/some_unknown_route, with body ''",
2282 message
2283 );
2284 }
2285}
2286
2287#[cfg(test)]
2288mod test_expect_failure {
2289 use crate::TestServer;
2290 use crate::testing::catch_panic_error_message_async;
2291 use axum::Router;
2292 use axum::routing::get;
2293 use http::StatusCode;
2294 use pretty_assertions::assert_str_eq;
2295
2296 #[tokio::test]
2297 async fn it_should_not_panic_if_expect_failure_on_404() {
2298 let app = Router::new();
2300
2301 let mut server = TestServer::new(app);
2303 server.expect_failure();
2304
2305 server.get(&"/some_unknown_route").await;
2307 }
2308
2309 #[tokio::test]
2310 async fn it_should_panic_if_success_is_returned() {
2311 async fn get_ping() -> &'static str {
2312 "pong!"
2313 }
2314
2315 let app = Router::new().route("/ping", get(get_ping));
2317
2318 let mut server = TestServer::new(app);
2320 server.expect_failure();
2321
2322 let message = catch_panic_error_message_async(server.get(&"/ping")).await;
2324 assert_str_eq!(
2325 "Expect status code outside 2xx range, received 200 (OK), for request GET http://localhost/ping, with body 'pong!'",
2326 message
2327 );
2328 }
2329
2330 #[tokio::test]
2331 async fn it_should_panic_on_other_2xx_status_code() {
2332 async fn get_accepted() -> StatusCode {
2333 StatusCode::ACCEPTED
2334 }
2335
2336 let app = Router::new().route("/accepted", get(get_accepted));
2338
2339 let mut server = TestServer::new(app);
2341 server.expect_failure();
2342
2343 let message = catch_panic_error_message_async(server.get(&"/accepted")).await;
2345 assert_str_eq!(
2346 "Expect status code outside 2xx range, received 202 (Accepted), for request GET http://localhost/accepted, with body ''",
2347 message
2348 );
2349 }
2350}
2351
2352#[cfg(feature = "typed-routing")]
2353#[cfg(test)]
2354mod test_typed_get {
2355 use super::*;
2356 use axum::Router;
2357 use axum_extra::routing::RouterExt;
2358 use serde::Deserialize;
2359
2360 #[derive(TypedPath, Deserialize)]
2361 #[typed_path("/path/{id}")]
2362 struct TestingPath {
2363 id: u32,
2364 }
2365
2366 async fn route_get(TestingPath { id }: TestingPath) -> String {
2367 format!("get {id}")
2368 }
2369
2370 fn new_app() -> Router {
2371 Router::new().typed_get(route_get)
2372 }
2373
2374 #[tokio::test]
2375 async fn it_should_send_get() {
2376 let server = TestServer::new(new_app());
2377
2378 server
2379 .typed_get(&TestingPath { id: 123 })
2380 .await
2381 .assert_text("get 123");
2382 }
2383}
2384
2385#[cfg(feature = "typed-routing")]
2386#[cfg(test)]
2387mod test_typed_post {
2388 use super::*;
2389 use axum::Router;
2390 use axum_extra::routing::RouterExt;
2391 use serde::Deserialize;
2392
2393 #[derive(TypedPath, Deserialize)]
2394 #[typed_path("/path/{id}")]
2395 struct TestingPath {
2396 id: u32,
2397 }
2398
2399 async fn route_post(TestingPath { id }: TestingPath) -> String {
2400 format!("post {id}")
2401 }
2402
2403 fn new_app() -> Router {
2404 Router::new().typed_post(route_post)
2405 }
2406
2407 #[tokio::test]
2408 async fn it_should_send_post() {
2409 let server = TestServer::new(new_app());
2410
2411 server
2412 .typed_post(&TestingPath { id: 123 })
2413 .await
2414 .assert_text("post 123");
2415 }
2416}
2417
2418#[cfg(feature = "typed-routing")]
2419#[cfg(test)]
2420mod test_typed_patch {
2421 use super::*;
2422 use axum::Router;
2423 use axum_extra::routing::RouterExt;
2424 use serde::Deserialize;
2425
2426 #[derive(TypedPath, Deserialize)]
2427 #[typed_path("/path/{id}")]
2428 struct TestingPath {
2429 id: u32,
2430 }
2431
2432 async fn route_patch(TestingPath { id }: TestingPath) -> String {
2433 format!("patch {id}")
2434 }
2435
2436 fn new_app() -> Router {
2437 Router::new().typed_patch(route_patch)
2438 }
2439
2440 #[tokio::test]
2441 async fn it_should_send_patch() {
2442 let server = TestServer::new(new_app());
2443
2444 server
2445 .typed_patch(&TestingPath { id: 123 })
2446 .await
2447 .assert_text("patch 123");
2448 }
2449}
2450
2451#[cfg(feature = "typed-routing")]
2452#[cfg(test)]
2453mod test_typed_put {
2454 use super::*;
2455 use axum::Router;
2456 use axum_extra::routing::RouterExt;
2457 use serde::Deserialize;
2458
2459 #[derive(TypedPath, Deserialize)]
2460 #[typed_path("/path/{id}")]
2461 struct TestingPath {
2462 id: u32,
2463 }
2464
2465 async fn route_put(TestingPath { id }: TestingPath) -> String {
2466 format!("put {id}")
2467 }
2468
2469 fn new_app() -> Router {
2470 Router::new().typed_put(route_put)
2471 }
2472
2473 #[tokio::test]
2474 async fn it_should_send_put() {
2475 let server = TestServer::new(new_app());
2476
2477 server
2478 .typed_put(&TestingPath { id: 123 })
2479 .await
2480 .assert_text("put 123");
2481 }
2482}
2483
2484#[cfg(feature = "typed-routing")]
2485#[cfg(test)]
2486mod test_typed_delete {
2487 use super::*;
2488 use axum::Router;
2489 use axum_extra::routing::RouterExt;
2490 use serde::Deserialize;
2491
2492 #[derive(TypedPath, Deserialize)]
2493 #[typed_path("/path/{id}")]
2494 struct TestingPath {
2495 id: u32,
2496 }
2497
2498 async fn route_delete(TestingPath { id }: TestingPath) -> String {
2499 format!("delete {id}")
2500 }
2501
2502 fn new_app() -> Router {
2503 Router::new().typed_delete(route_delete)
2504 }
2505
2506 #[tokio::test]
2507 async fn it_should_send_delete() {
2508 let server = TestServer::new(new_app());
2509
2510 server
2511 .typed_delete(&TestingPath { id: 123 })
2512 .await
2513 .assert_text("delete 123");
2514 }
2515}
2516
2517#[cfg(feature = "typed-routing")]
2518#[cfg(test)]
2519mod test_typed_method {
2520 use super::*;
2521 use axum::Router;
2522 use axum_extra::routing::RouterExt;
2523 use serde::Deserialize;
2524
2525 #[derive(TypedPath, Deserialize)]
2526 #[typed_path("/path/{id}")]
2527 struct TestingPath {
2528 id: u32,
2529 }
2530
2531 async fn route_get(TestingPath { id }: TestingPath) -> String {
2532 format!("get {id}")
2533 }
2534
2535 async fn route_post(TestingPath { id }: TestingPath) -> String {
2536 format!("post {id}")
2537 }
2538
2539 async fn route_patch(TestingPath { id }: TestingPath) -> String {
2540 format!("patch {id}")
2541 }
2542
2543 async fn route_put(TestingPath { id }: TestingPath) -> String {
2544 format!("put {id}")
2545 }
2546
2547 async fn route_delete(TestingPath { id }: TestingPath) -> String {
2548 format!("delete {id}")
2549 }
2550
2551 fn new_app() -> Router {
2552 Router::new()
2553 .typed_get(route_get)
2554 .typed_post(route_post)
2555 .typed_patch(route_patch)
2556 .typed_put(route_put)
2557 .typed_delete(route_delete)
2558 }
2559
2560 #[tokio::test]
2561 async fn it_should_send_get() {
2562 let server = TestServer::new(new_app());
2563
2564 server
2565 .typed_method(Method::GET, &TestingPath { id: 123 })
2566 .await
2567 .assert_text("get 123");
2568 }
2569
2570 #[tokio::test]
2571 async fn it_should_send_post() {
2572 let server = TestServer::new(new_app());
2573
2574 server
2575 .typed_method(Method::POST, &TestingPath { id: 123 })
2576 .await
2577 .assert_text("post 123");
2578 }
2579
2580 #[tokio::test]
2581 async fn it_should_send_patch() {
2582 let server = TestServer::new(new_app());
2583
2584 server
2585 .typed_method(Method::PATCH, &TestingPath { id: 123 })
2586 .await
2587 .assert_text("patch 123");
2588 }
2589
2590 #[tokio::test]
2591 async fn it_should_send_put() {
2592 let server = TestServer::new(new_app());
2593
2594 server
2595 .typed_method(Method::PUT, &TestingPath { id: 123 })
2596 .await
2597 .assert_text("put 123");
2598 }
2599
2600 #[tokio::test]
2601 async fn it_should_send_delete() {
2602 let server = TestServer::new(new_app());
2603
2604 server
2605 .typed_method(Method::DELETE, &TestingPath { id: 123 })
2606 .await
2607 .assert_text("delete 123");
2608 }
2609}
2610
2611#[cfg(test)]
2612mod test_sync {
2613 use super::*;
2614 use axum::Router;
2615 use axum::routing::get;
2616 use std::cell::OnceCell;
2617
2618 #[tokio::test]
2619 async fn it_should_be_able_to_be_in_one_cell() {
2620 let cell: OnceCell<TestServer> = OnceCell::new();
2621 let server = cell.get_or_init(|| {
2622 async fn route_get() -> &'static str {
2623 "it works"
2624 }
2625
2626 let router = Router::new().route("/test", get(route_get));
2627
2628 TestServer::new(router)
2629 });
2630
2631 server.get("/test").await.assert_text("it works");
2632 }
2633}
2634
2635#[cfg(test)]
2636mod test_is_running {
2637 use super::*;
2638 use crate::testing::catch_panic_error_message_async;
2639 use crate::util::new_random_tokio_tcp_listener_with_socket_addr;
2640 use axum::Router;
2641 use axum::routing::IntoMakeService;
2642 use axum::routing::get;
2643 use axum::serve;
2644 use pretty_assertions::assert_str_eq;
2645 use std::time::Duration;
2646 use tokio::sync::Notify;
2647 use tokio::time::sleep;
2648
2649 async fn get_ping() -> &'static str {
2650 "pong!"
2651 }
2652
2653 #[tokio::test]
2654 async fn it_should_panic_when_run_with_mock_http() {
2655 let shutdown_notification = Arc::new(Notify::new());
2656 let waiting_notification = shutdown_notification.clone();
2657
2658 let app: IntoMakeService<Router> = Router::new()
2660 .route("/ping", get(get_ping))
2661 .into_make_service();
2662 let (listener, ip_port) = new_random_tokio_tcp_listener_with_socket_addr().unwrap();
2663 let application = serve(listener, app)
2664 .with_graceful_shutdown(async move { waiting_notification.notified().await });
2665
2666 let server = TestServer::builder().build(application);
2668
2669 server.get("/ping").await.assert_status_ok();
2670 assert!(server.is_running());
2671
2672 shutdown_notification.notify_one();
2673 sleep(Duration::from_millis(10)).await;
2674
2675 assert!(!server.is_running());
2676
2677 let ip = ip_port.ip();
2678 let port = ip_port.port();
2679 let expected = format!(
2680 "Sending request failed, for request GET http://{ip}:{port}/ping,
2681 client error (Connect)
2682 tcp connect error
2683 Connection refused (os error 61)
2684"
2685 );
2686 let message = catch_panic_error_message_async(server.get("/ping")).await;
2687 assert_str_eq!(expected, message);
2688 }
2689}
2690
2691#[cfg(test)]
2692mod test_save_cookies {
2693 use crate::TestServer;
2694 use axum::Router;
2695 use axum::extract::Request;
2696 use axum::http::header::HeaderMap;
2697 use axum::routing::get;
2698 use axum::routing::put;
2699 use axum_extra::extract::cookie::CookieJar as AxumCookieJar;
2700 use cookie::Cookie;
2701 use cookie::SameSite;
2702 use http_body_util::BodyExt;
2703
2704 const TEST_COOKIE_NAME: &'static str = &"test-cookie";
2705
2706 #[tokio::test]
2707 async fn it_should_save_cookies_across_requests_when_enabled() {
2708 let mut server = TestServer::new(app());
2709
2710 server.save_cookies();
2711
2712 save_cookie_using_axum_test(&server).await;
2713 assert_cookie_using_axum_test(&server).await;
2714 }
2715
2716 #[cfg(feature = "reqwest")]
2717 #[tokio::test]
2718 async fn it_should_save_cookies_across_reqwest_requests_when_enabled() {
2719 let mut server = TestServer::builder().http_transport().build(app());
2720
2721 server.save_cookies();
2722
2723 save_cookie_using_reqwest(&server).await;
2724 save_cookie_using_reqwest(&server).await;
2725 }
2726
2727 #[tokio::test]
2728 async fn it_should_save_cookies_across_axum_test_requests_when_enabled_for_second_request() {
2729 let mut server = TestServer::builder().http_transport().build(app());
2730
2731 save_cookie_using_axum_test(&server).await;
2732 assert_no_cookie_using_axum_test(&server).await;
2733
2734 server.save_cookies();
2735
2736 save_cookie_using_axum_test(&server).await;
2737 assert_cookie_using_axum_test(&server).await;
2738 }
2739
2740 #[cfg(feature = "reqwest")]
2741 #[tokio::test]
2742 async fn it_should_save_cookies_across_reqwest_requests_when_enabled_for_second_request() {
2743 let mut server = TestServer::builder().http_transport().build(app());
2744
2745 save_cookie_using_reqwest(&server).await;
2746 assert_no_cookie_using_reqwest(&server).await;
2747
2748 server.save_cookies();
2749
2750 save_cookie_using_reqwest(&server).await;
2751 assert_cookie_using_reqwest(&server).await;
2752 }
2753
2754 #[cfg(feature = "reqwest")]
2755 #[tokio::test]
2756 async fn it_should_save_cookies_when_set_by_reqwest_and_read_by_axum_test() {
2757 let mut server = TestServer::builder().http_transport().build(app());
2758
2759 server.save_cookies();
2760
2761 save_cookie_using_reqwest(&server).await;
2762 assert_cookie_using_axum_test(&server).await;
2763 }
2764
2765 #[cfg(feature = "reqwest")]
2766 #[tokio::test]
2767 async fn it_should_save_cookies_when_set_by_axum_test_and_read_by_reqwest() {
2768 let mut server = TestServer::builder().http_transport().build(app());
2769
2770 server.save_cookies();
2771
2772 save_cookie_using_axum_test(&server).await;
2773 assert_cookie_using_reqwest(&server).await;
2774 }
2775
2776 fn app() -> Router {
2777 async fn put_cookie_with_attributes(
2778 mut cookies: AxumCookieJar,
2779 request: Request,
2780 ) -> (AxumCookieJar, &'static str) {
2781 let body_bytes = request
2782 .into_body()
2783 .collect()
2784 .await
2785 .expect("Should turn the body into bytes")
2786 .to_bytes();
2787
2788 let body_text: String = String::from_utf8_lossy(&body_bytes).to_string();
2789 let cookie = Cookie::build((TEST_COOKIE_NAME, body_text))
2790 .http_only(true)
2791 .secure(true)
2792 .same_site(SameSite::Strict)
2793 .path("/cookie")
2794 .build();
2795 cookies = cookies.add(cookie);
2796
2797 (cookies, &"done")
2798 }
2799
2800 async fn get_cookie_headers_joined(headers: HeaderMap) -> String {
2801 let cookies: String = headers
2802 .get_all("cookie")
2803 .into_iter()
2804 .map(|c| c.to_str().unwrap_or("").to_string())
2805 .reduce(|a, b| a + "; " + &b)
2806 .unwrap_or_else(|| String::new());
2807
2808 cookies
2809 }
2810
2811 Router::new()
2812 .route("/cookie", put(put_cookie_with_attributes))
2813 .route("/cookie", get(get_cookie_headers_joined))
2814 }
2815
2816 async fn save_cookie_using_axum_test(server: &TestServer) {
2817 server.put(&"/cookie").text(&"cookie-found!").await;
2818 }
2819
2820 #[cfg(feature = "reqwest")]
2821 async fn save_cookie_using_reqwest(server: &TestServer) {
2822 server
2823 .reqwest_put(&"/cookie")
2824 .body("cookie-found!".to_string())
2825 .send()
2826 .await
2827 .unwrap();
2828 }
2829
2830 async fn assert_cookie_using_axum_test(server: &TestServer) {
2831 server
2832 .get(&"/cookie")
2833 .await
2834 .assert_text("test-cookie=cookie-found!");
2835 }
2836
2837 #[cfg(feature = "reqwest")]
2838 async fn assert_cookie_using_reqwest(server: &TestServer) {
2839 let response_text = server
2840 .reqwest_get(&"/cookie")
2841 .send()
2842 .await
2843 .unwrap()
2844 .text()
2845 .await
2846 .unwrap();
2847
2848 assert_eq!("test-cookie=cookie-found!", response_text);
2849 }
2850
2851 async fn assert_no_cookie_using_axum_test(server: &TestServer) {
2852 server.get(&"/cookie").await.assert_text("");
2853 }
2854
2855 #[cfg(feature = "reqwest")]
2856 async fn assert_no_cookie_using_reqwest(server: &TestServer) {
2857 let response_text = server
2858 .reqwest_get(&"/cookie")
2859 .send()
2860 .await
2861 .unwrap()
2862 .text()
2863 .await
2864 .unwrap();
2865
2866 assert_eq!("", response_text);
2867 }
2868}