Skip to main content

axum_test/
test_server.rs

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///
44/// The `TestServer` runs your Axum application,
45/// allowing you to make HTTP requests against it.
46///
47/// # Building
48///
49/// A `TestServer` can be used to run an [`axum::Router`], an [`::axum::routing::IntoMakeService`],
50/// and others.
51///
52/// The most straight forward approach is to call [`TestServer::new`],
53/// and pass in your application:
54///
55/// ```rust
56/// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
57/// #
58/// use axum::Router;
59/// use axum::routing::get;
60///
61/// use axum_test::TestServer;
62///
63/// let app = Router::new()
64///     .route(&"/hello", get(|| async { "hello!" }));
65///
66/// let server = TestServer::new(app);
67/// #
68/// # Ok(())
69/// # }
70/// ```
71///
72/// # Requests
73///
74/// Requests are built by calling [`TestServer::get()`](crate::TestServer::get()),
75/// [`TestServer::post()`](crate::TestServer::post()), [`TestServer::put()`](crate::TestServer::put()),
76/// [`TestServer::delete()`](crate::TestServer::delete()), and [`TestServer::patch()`](crate::TestServer::patch()) methods.
77/// Each returns a [`TestRequest`](crate::TestRequest), which allows for customising the request content.
78///
79/// For example:
80///
81/// ```rust
82/// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
83/// #
84/// use axum::Router;
85/// use axum::routing::get;
86///
87/// use axum_test::TestServer;
88///
89/// let app = Router::new()
90///     .route(&"/hello", get(|| async { "hello!" }));
91///
92/// let server = TestServer::new(app);
93///
94/// let response = server.get(&"/hello")
95///     .authorization_bearer("password12345")
96///     .add_header("x-custom-header", "custom-value")
97///     .await;
98///
99/// response.assert_text("hello!");
100/// #
101/// # Ok(())
102/// # }
103/// ```
104///
105/// Request methods also exist for using Axum Extra [`axum_extra::routing::TypedPath`],
106/// or for building Reqwest [`reqwest::RequestBuilder`]. See those methods for detauls.
107///
108/// # Customising
109///
110/// A `TestServer` can be built from a builder, by calling [`TestServer::builder`],
111/// and customising settings. This allows one to set **mocked** (default when possible)
112/// or **real http** networking for your service.
113///
114/// ```rust
115/// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
116/// #
117/// use axum::Router;
118/// use axum::routing::get;
119///
120/// use axum_test::TestServer;
121///
122/// let app = Router::new()
123///     .route(&"/hello", get(|| async { "hello!" }));
124///
125/// // Customise server when building
126/// let mut server = TestServer::builder()
127///     .http_transport()
128///     .expect_success_by_default()
129///     .save_cookies()
130///     .build(app);
131///
132/// // Add items to be sent on _all_ all requests
133/// server.add_header("x-custom-for-all", "common-value");
134///
135/// let response = server.get("/hello").await;
136/// #
137/// # Ok(())
138/// # }
139/// ```
140///
141#[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    /// A helper function to create a builder for creating a [`TestServer`].
156    pub fn builder() -> TestServerBuilder {
157        TestServerBuilder::default()
158    }
159
160    /// This will run the given Axum app,
161    /// allowing you to make requests against it.
162    ///
163    /// This is the same as creating a new `TestServer` with a configuration,
164    /// and passing [`TestServerConfig::default()`].
165    ///
166    /// Note: this will panic if the `TestServer` cannot be built.
167    /// To catch the error use [`TestServer::try_new`].
168    ///
169    /// ```rust
170    /// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
171    /// #
172    /// use axum::Router;
173    /// use axum::routing::get;
174    /// use axum_test::TestServer;
175    ///
176    /// let app = Router::new()
177    ///     .route(&"/hello", get(|| async { "hello!" }));
178    ///
179    /// let server = TestServer::new(app);
180    /// #
181    /// # Ok(())
182    /// # }
183    /// ```
184    ///
185    /// The type of applications that can be passed in include:
186    ///
187    ///  - [`axum::Router`]
188    ///  - [`axum::routing::IntoMakeService`]
189    ///  - [`axum::extract::connect_info::IntoMakeServiceWithConnectInfo`]
190    ///  - [`axum::serve::Serve`]
191    ///  - [`axum::serve::WithGracefulShutdown`]
192    ///  - A function returning an [`actix_web::App`]
193    ///
194    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    /// Attempts to create a [`TestServer`], and returns an error if this fails.
202    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    /// Similar to [`TestServer::new()`], with a customised configuration.
210    /// This includes type of transport in use (i.e. specify a specific port),
211    /// or change default settings (like the default content type for requests).
212    ///
213    /// This can take a [`TestServerConfig`] or a [`TestServerBuilder`].
214    /// See those for more information on configuration settings.
215    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    /// Attempts to create a [`TestServer`], and returns an error if this fails.
224    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    /// Creates a HTTP GET request to the path.
278    pub fn get(&self, path: &str) -> TestRequest {
279        self.method(Method::GET, path)
280    }
281
282    /// Creates a HTTP POST request to the given path.
283    pub fn post(&self, path: &str) -> TestRequest {
284        self.method(Method::POST, path)
285    }
286
287    /// Creates a HTTP QUERY request to the given path.
288    pub fn query(&self, path: &str) -> TestRequest {
289        self.method(Method::QUERY, path)
290    }
291
292    /// Creates a HTTP PATCH request to the path.
293    pub fn patch(&self, path: &str) -> TestRequest {
294        self.method(Method::PATCH, path)
295    }
296
297    /// Creates a HTTP PUT request to the path.
298    pub fn put(&self, path: &str) -> TestRequest {
299        self.method(Method::PUT, path)
300    }
301
302    /// Creates a HTTP DELETE request to the path.
303    pub fn delete(&self, path: &str) -> TestRequest {
304        self.method(Method::DELETE, path)
305    }
306
307    /// Creates a HTTP request, to the method and path provided.
308    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    /// Creates a HTTP request, using Reqwest, using the method + path described.
362    /// This expects a relative url to the `TestServer`.
363    ///
364    /// ```rust
365    /// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
366    /// #
367    /// use axum::Router;
368    /// use axum_test::TestServer;
369    ///
370    /// let my_app = Router::new();
371    /// let server = TestServer::builder()
372    ///     .http_transport() // Important, must be HTTP!
373    ///     .build(my_app);
374    ///
375    /// // Build your request
376    /// let request = server.get(&"/user")
377    ///     .add_header("x-custom-header", "example.com")
378    ///     .content_type("application/yaml");
379    ///
380    /// // await request to execute
381    /// let response = request.await;
382    /// #
383    /// # Ok(()) }
384    /// ```
385    #[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    /// Creates a request to the server, to start a Websocket connection,
395    /// on the path given.
396    ///
397    /// This is the requivalent of making a GET request to the endpoint,
398    /// and setting the various headers needed for making an upgrade request.
399    ///
400    /// *Note*, this requires the server to be running on a real HTTP
401    /// port. Either using a randomly assigned port, or a specified one.
402    /// See the [`TestServerConfig::transport`](crate::TestServerConfig::transport) for more details.
403    ///
404    /// # Example
405    ///
406    /// ```rust
407    /// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
408    /// #
409    /// use axum::Router;
410    /// use axum_test::TestServer;
411    ///
412    /// let app = Router::new();
413    /// let server = TestServer::builder()
414    ///     .http_transport()
415    ///     .build(app);
416    ///
417    /// let mut websocket = server
418    ///     .get_websocket(&"/my-web-socket-end-point")
419    ///     .await
420    ///     .into_websocket()
421    ///     .await;
422    ///
423    /// websocket.send_text("Hello!").await;
424    /// #
425    /// # Ok(()) }
426    /// ```
427    ///
428    #[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    /// Creates a HTTP GET request, using the typed path provided.
443    ///
444    /// See [`axum-extra`](https://docs.rs/axum-extra) for full documentation on [`TypedPath`](axum_extra::routing::TypedPath).
445    ///
446    /// # Example Test
447    ///
448    /// Using a `TypedPath` you can write build and test a route like below:
449    ///
450    /// ```rust
451    /// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
452    /// #
453    /// use axum::Json;
454    /// use axum::Router;
455    /// use axum::routing::get;
456    /// use axum_extra::routing::RouterExt;
457    /// use axum_extra::routing::TypedPath;
458    /// use serde::Deserialize;
459    /// use serde::Serialize;
460    ///
461    /// use axum_test::TestServer;
462    ///
463    /// #[derive(TypedPath, Deserialize)]
464    /// #[typed_path("/users/{user_id}")]
465    /// struct UserPath {
466    ///     pub user_id: u32,
467    /// }
468    ///
469    /// // Build a typed route:
470    /// async fn route_get_user(UserPath { user_id }: UserPath) -> String {
471    ///     format!("hello user {user_id}")
472    /// }
473    ///
474    /// let app = Router::new()
475    ///     .typed_get(route_get_user);
476    ///
477    /// // Then test the route:
478    /// let server = TestServer::new(app);
479    /// server
480    ///     .typed_get(&UserPath { user_id: 123 })
481    ///     .await
482    ///     .assert_text("hello user 123");
483    /// #
484    /// # Ok(())
485    /// # }
486    /// ```
487    ///
488    #[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    /// Creates a HTTP POST request, using the typed path provided.
497    ///
498    /// See [`axum-extra`](https://docs.rs/axum-extra) for full documentation on [`TypedPath`](axum_extra::routing::TypedPath).
499    #[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    /// Creates a HTTP PATCH request, using the typed path provided.
508    ///
509    /// See [`axum-extra`](https://docs.rs/axum-extra) for full documentation on [`TypedPath`](axum_extra::routing::TypedPath).
510    #[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    /// Creates a HTTP PUT request, using the typed path provided.
519    ///
520    /// See [`axum-extra`](https://docs.rs/axum-extra) for full documentation on [`TypedPath`](axum_extra::routing::TypedPath).
521    #[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    /// Creates a HTTP DELETE request, using the typed path provided.
530    ///
531    /// See [`axum-extra`](https://docs.rs/axum-extra) for full documentation on [`TypedPath`](axum_extra::routing::TypedPath).
532    #[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    /// Creates a typed HTTP request, using the method provided.
541    ///
542    /// See [`axum-extra`](https://docs.rs/axum-extra) for full documentation on [`TypedPath`](axum_extra::routing::TypedPath).
543    #[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    /// Returns the local web address for the test server,
552    /// if an address is available.
553    ///
554    /// The address is available when running as a real web server,
555    /// by setting the [`TestServerConfig`](crate::TestServerConfig) `transport` field to `Transport::HttpRandomPort` or `Transport::HttpIpPort`.
556    ///
557    /// This will return `None` when there is mock HTTP transport (the default).
558    pub fn server_address(&self) -> Option<Url> {
559        self.url()
560    }
561
562    /// This turns a relative path, into an absolute path to the server.
563    /// i.e. A path like `/users/123` will become something like `http://127.0.0.1:1234/users/123`.
564    ///
565    /// The absolute address can be used to make requests to the running server,
566    /// using any appropriate client you wish.
567    ///
568    /// # Example
569    ///
570    /// ```rust
571    /// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
572    /// #
573    /// use axum::Router;
574    /// use axum_test::TestServer;
575    ///
576    /// let app = Router::new();
577    /// let server = TestServer::builder()
578    ///         .http_transport()
579    ///         .build(app);
580    ///
581    /// let full_url = server.server_url(&"/users/123?filter=enabled")?;
582    ///
583    /// // Prints something like ... http://127.0.0.1:1234/users/123?filter=enabled
584    /// println!("{full_url}");
585    /// #
586    /// # Ok(()) }
587    /// ```
588    ///
589    /// This will return an error if you are using the mock transport.
590    /// Real HTTP transport is required to use this method (see [`TestServerConfig`](crate::TestServerConfig) `transport` field).
591    ///
592    /// It will also return an error if you provide an absolute path,
593    /// for example if you pass in `http://google.com`.
594    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        // Ensure the query params are present
618        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    /// Adds a single cookie to be included on *all* future requests.
626    ///
627    /// If a cookie with the same name already exists,
628    /// then it will be replaced.
629    pub fn add_cookie(&mut self, cookie: Cookie) {
630        self.cookie_jar.add_cookie(cookie);
631    }
632
633    /// Adds extra cookies to be used on *all* future requests.
634    ///
635    /// Any cookies which have the same name as the new cookies,
636    /// will get replaced.
637    pub fn add_cookies(&mut self, cookies: CookieJar) {
638        self.cookie_jar.add_cookies_by_jar(cookies);
639    }
640
641    /// Clears all of the cookies stored internally.
642    pub fn clear_cookies(&mut self) {
643        self.cookie_jar.clear_cookies();
644    }
645
646    /// Requests made using this `TestServer` will save their cookies for future requests to send.
647    /// Including sharing cookies with requests made using the `reqwest` feature.
648    ///
649    /// This behaviour is off by default.
650    pub fn save_cookies(&mut self) {
651        self.cookie_jar.enable_saving();
652    }
653
654    /// Requests made using this `TestServer` will _not_ save their cookies for future requests to send up.
655    /// Including sharing cookies with requests made using the `reqwest` feature.
656    ///
657    /// This is the default behaviour.
658    pub fn do_not_save_cookies(&mut self) {
659        self.cookie_jar.disable_saving();
660    }
661
662    /// Requests made using this `TestServer` will assert a HTTP status in the 2xx range will be returned, unless marked otherwise.
663    ///
664    /// By default this behaviour is off.
665    pub fn expect_success(&mut self) {
666        self.expected_state = ExpectedState::Success;
667    }
668
669    /// Requests made using this `TestServer` will assert a HTTP status is outside the 2xx range will be returned, unless marked otherwise.
670    ///
671    /// By default this behaviour is off.
672    pub fn expect_failure(&mut self) {
673        self.expected_state = ExpectedState::Failure;
674    }
675
676    /// Adds a query parameter to be sent on *all* future requests.
677    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    /// Adds query parameters to be sent on *all* future requests.
687    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    /// Adds a raw query param, with no urlencoding of any kind,
697    /// to be send on *all* future requests.
698    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    /// Clears all query params set.
703    pub fn clear_query_params(&mut self) {
704        self.state.clear_query_params();
705    }
706
707    /// Adds a header to be sent with all future requests built from this `TestServer`.
708    ///
709    /// ```rust
710    /// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
711    /// #
712    /// use axum::Router;
713    /// use axum_test::TestServer;
714    ///
715    /// let app = Router::new();
716    /// let mut server = TestServer::new(app);
717    ///
718    /// server.add_header("x-custom-header", "custom-value");
719    /// server.add_header(http::header::CONTENT_LENGTH, 12345);
720    /// server.add_header(http::header::HOST, "example.com");
721    ///
722    /// let response = server.get(&"/my-end-point")
723    ///     .await;
724    /// #
725    /// # Ok(()) }
726    /// ```
727    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    /// Clears all headers set so far.
745    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            // These are copied over from the cookie jar,
771            // as the server could change it's save state after the request is made.
772            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    /// Returns true or false if the underlying service inside the `TestServer`
786    /// is still running. For many types of services this will always return `true`.
787    ///
788    /// When a `TestServer` is built using [`axum::serve::WithGracefulShutdown`],
789    /// this will return false if the service has shutdown.
790    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 there is a scheme, then this is an absolute path.
804    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            // We only set the host/port if the scheme is also present.
816            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                // todo, add username:password support
823            }
824        }
825    }
826
827    // Why does this exist?
828    //
829    // This exists to allow `server.get("/users")` and `server.get("users")` (without a slash)
830    // to go to the same place.
831    //
832    // It does this by saying ...
833    //  - if there is a scheme, it's a full path.
834    //  - if no scheme, it must be a path
835    //
836    if is_absolute_uri(&path_uri) {
837        url.set_path(path_uri.path());
838
839        // In this path we are replacing, so drop any query params on the original url.
840        if url.query().is_some() {
841            url.set_query(None);
842        }
843    } else {
844        // Grab everything up until the query parameters, or everything after that
845        let calculated_path = path.split('?').next().unwrap_or(path);
846        url.set_path(calculated_path);
847
848        // Move any query parameters from the url to the query params store.
849        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        // Build an application with a route.
1099        let app = Router::new()
1100            .route("/ping", get(get_ping))
1101            .into_make_service_with_connect_info::<SocketAddr>();
1102
1103        // Run the server.
1104        let server = TestServer::new(app);
1105
1106        // Get the request.
1107        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        // Get the request _with_ slash
1130        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        // Get the request _without_ slash
1139        server.get(&"ping").await.assert_text(&"pong!");
1140    }
1141
1142    #[tokio::test]
1143    async fn it_should_get_using_absolute_path() {
1144        // Build an application with a route.
1145        let app = Router::new().route("/ping", get(get_ping));
1146
1147        // Reserve an address
1148        let reserved_address = ReservedSocketAddr::reserve_random_socket_addr().unwrap();
1149        let ip = reserved_address.ip();
1150        let port = reserved_address.port();
1151
1152        // Run the server.
1153        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        // Get the request.
1159        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        // Build an application with a route.
1170        let app = Router::new().route("/ping", get(get_ping));
1171
1172        // Reserve an IP / Port
1173        let reserved_address = ReservedSocketAddr::reserve_random_socket_addr().unwrap();
1174        let ip = reserved_address.ip();
1175        let port = reserved_address.port();
1176
1177        // Run the server.
1178        let server = TestServer::builder()
1179            .http_transport_with_ip_port(Some(ip), Some(port))
1180            .restrict_requests_with_http_scheme() // Key part of the test!
1181            .try_build(app)
1182            .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1183
1184        // Get the request.
1185        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        // Build an application with a route.
1196        let app = Router::new().route("/ping", get(get_ping));
1197
1198        // Reserve an IP / Port
1199        let reserved_address = ReservedSocketAddr::reserve_random_socket_addr().unwrap();
1200        let ip = reserved_address.ip();
1201        let mut port = reserved_address.port();
1202
1203        // Run the server.
1204        let server = TestServer::builder()
1205            .http_transport_with_ip_port(Some(ip), Some(port))
1206            .restrict_requests_with_http_scheme() // Key part of the test!
1207            .try_build(app)
1208            .error_message_fn(|| format!("Should create test server with address {}:{}", ip, port));
1209
1210        // Get the request.
1211        port += 1; // << Change the port to be off by one and not match the server
1212        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        // Build an application with a route.
1352        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        // Build an application with a route.
1400        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        // Build an application with a route.
1430        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        // Build an application with a route.
1444        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        // Build an application with a route.
1470        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        // Build an application with a route.
1495        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        // Build an application with a route.
1523        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        // Build an application with a route.
1548        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    // TODO, change this behaviour to allow an empty path. It should be the same as no path at all.
1561    #[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        // Build an application with a route.
1568        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 expected_url = format!("http://{}:{}", ip, reserved_port.port());
1575        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        // Build cookies to send up
1639        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        // The important bit of this test
1688        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        // Build an application with a route.
1734        let app = Router::new().route("/header", get(ping_header));
1735
1736        // Run the server.
1737        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        // Send a request with the header
1744        let response = server.get(&"/header").await;
1745
1746        // Check it sent back the right text
1747        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        // Build an application with a route.
1791        let app = Router::new().route("/header", get(ping_header));
1792
1793        // Run the server.
1794        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        // Send a request with the header
1802        let response = server.get(&"/header").await;
1803
1804        // Check it sent back the right text
1805        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        // Build an application with a route.
1844        let app = Router::new().route("/query", get(get_query_param));
1845
1846        // Run the server.
1847        let mut server = TestServer::new(app);
1848        server.add_query_params(QueryParam {
1849            message: "it works".to_string(),
1850        });
1851
1852        // Get the request.
1853        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        // Build an application with a route.
1859        let app = Router::new().route("/query", get(get_query_param));
1860
1861        // Run the server.
1862        let mut server = TestServer::new(app);
1863        server.add_query_params(&[("message", "it works")]);
1864
1865        // Get the request.
1866        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        // Build an application with a route.
1872        let app = Router::new().route("/query-2", get(get_query_param_2));
1873
1874        // Run the server.
1875        let mut server = TestServer::new(app);
1876        server.add_query_params(&[("message", "it works"), ("other", "yup")]);
1877
1878        // Get the request.
1879        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        // Build an application with a route.
1885        let app = Router::new().route("/query-2", get(get_query_param_2));
1886
1887        // Run the server.
1888        let mut server = TestServer::new(app);
1889        server.add_query_params(&[("message", "it works")]);
1890        server.add_query_params(&[("other", "yup")]);
1891
1892        // Get the request.
1893        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        // Build an application with a route.
1899        let app = Router::new().route("/query-2", get(get_query_param_2));
1900
1901        // Run the server.
1902        let mut server = TestServer::new(app);
1903        server.add_query_params(json!({
1904            "message": "it works",
1905            "other": "yup"
1906        }));
1907
1908        // Get the request.
1909        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        // Build an application with a route.
1946        let app = Router::new().route("/query", get(get_query_param));
1947
1948        // Run the server.
1949        let mut server = TestServer::new(app);
1950        server.add_query_param("message", "it works");
1951
1952        // Get the request.
1953        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        // Build an application with a route.
1959        let app = Router::new().route("/query-2", get(get_query_param_2));
1960
1961        // Run the server.
1962        let mut server = TestServer::new(app);
1963        server.add_query_param("message", "it works");
1964        server.add_query_param("other", "yup");
1965
1966        // Get the request.
1967        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        // Build an application with a route.
1973        let app = Router::new().route("/query-2", get(get_query_param_2));
1974
1975        // Run the server.
1976        let mut server = TestServer::new(app);
1977        server.add_query_param("message", "it works");
1978
1979        // Get the request.
1980        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        // Run the server.
2043        let mut server = TestServer::new(build_app());
2044        server.add_raw_query_param(&"message=it-works");
2045
2046        // Get the request.
2047        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        // Run the server.
2053        let mut server = TestServer::new(build_app());
2054        server.add_raw_query_param(&"items=one&items=two&items=three");
2055
2056        // Get the request.
2057        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        // Run the server.
2066        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        // Get the request.
2072        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        // Build an application with a route.
2107        let app = Router::new().route("/query", get(get_query_params));
2108
2109        // Run the server.
2110        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        // Get the request.
2118        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        // Build an application with a route.
2127        let app = Router::new().route("/query", get(get_query_params));
2128
2129        // Run the server.
2130        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        // Get the request.
2142        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        // Build an application with a route.
2212        let app = Router::new().route("/content_type", get(get_content_type));
2213
2214        // Run the server.
2215        let server = TestServer::builder()
2216            .default_content_type("text/plain")
2217            .build(app);
2218
2219        // Get the request.
2220        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        // Build an application with a route.
2242        let app = Router::new().route("/ping", get(get_ping));
2243
2244        // Run the server.
2245        let mut server = TestServer::new(app);
2246        server.expect_success();
2247
2248        // Get the request.
2249        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        // Build an application with a route.
2259        let app = Router::new().route("/accepted", get(get_accepted));
2260
2261        // Run the server.
2262        let mut server = TestServer::new(app);
2263        server.expect_success();
2264
2265        // Get the request.
2266        server.get(&"/accepted").await;
2267    }
2268
2269    #[tokio::test]
2270    async fn it_should_panic_on_404() {
2271        // Build an application with a route.
2272        let app = Router::new();
2273
2274        // Run the server.
2275        let mut server = TestServer::new(app);
2276        server.expect_success();
2277
2278        // Get the request.
2279        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        // Build an application with a route.
2299        let app = Router::new();
2300
2301        // Run the server.
2302        let mut server = TestServer::new(app);
2303        server.expect_failure();
2304
2305        // Get the request.
2306        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        // Build an application with a route.
2316        let app = Router::new().route("/ping", get(get_ping));
2317
2318        // Run the server.
2319        let mut server = TestServer::new(app);
2320        server.expect_failure();
2321
2322        // Get the request.
2323        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        // Build an application with a route.
2337        let app = Router::new().route("/accepted", get(get_accepted));
2338
2339        // Run the server.
2340        let mut server = TestServer::new(app);
2341        server.expect_failure();
2342
2343        // Get the request.
2344        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        // Build an application with a route.
2659        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        // Run the server.
2667        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}