Skip to main content

ntex/web/
test.rs

1//! Various helpers for ntex applications to use during testing.
2use std::sync::{Arc, Mutex, mpsc};
3use std::{convert::Infallible, fmt, future::Future, io, net, net::SocketAddr};
4use std::{pin::Pin, rc::Rc, thread, time};
5
6#[cfg(feature = "cookie")]
7use coo_kie::Cookie;
8use serde::{Serialize, de::DeserializeOwned};
9use urly::Url;
10use uuid::Uuid;
11
12use crate::client::error::ClientPayloadError;
13use crate::client::{Client, ClientConfig, ClientRequest, ClientResponse};
14use crate::error::Error;
15use crate::http::error::{HttpError, ResponseError};
16use crate::http::header::{CONTENT_TYPE, HeaderName, HeaderValue};
17use crate::http::test::TestRequest as HttpTestRequest;
18use crate::http::{self, HttpService, Method, Payload, Request, Response, StatusCode, Version};
19#[cfg(feature = "ws")]
20use crate::io::Sealed;
21use crate::router::{Path, ResourceDef};
22use crate::service::boxed::{self, BoxServiceFactory};
23use crate::service::{IntoServiceFactory, Pipeline, fn_service};
24use crate::time::{Millis, Seconds};
25use crate::util::{Bytes, BytesMut, Stream, stream_recv};
26#[cfg(feature = "ws")]
27use crate::ws::{WsClient, WsClientConfig, WsConnection, error::WsClientError};
28use crate::{Service, ServiceFactory, SharedCfg, io::IoConfig, rt::System, server::Server};
29
30use crate::web::config::WebAppConfig;
31use crate::web::httprequest::HttpRequest;
32use crate::web::rmap::ResourceMap;
33use crate::web::{FromRequest, HttpResponse, Responder, State, WebRequest, WebResponse};
34
35fn set_peer_addr(head: &mut http::Message<http::RequestHead>, addr: Option<SocketAddr>) {
36    #[derive(Debug)]
37    struct TestIo {
38        io: crate::io::Io,
39        _peer: crate::io::testing::IoTest,
40    }
41
42    impl http::IoAccess for TestIo {
43        fn get(&self) -> Option<&crate::io::IoRef> {
44            Some(self.io.as_ref())
45        }
46
47        fn take(&self) -> Option<(crate::io::IoBoxed, http::h1::Codec)> {
48            None
49        }
50    }
51
52    if let Some(addr) = addr {
53        let (client, server) = crate::io::testing::IoTest::create();
54        let io = crate::io::Io::new(server.set_peer_addr(addr), SharedCfg::default());
55        head.io = http::CurrentIo::new(Rc::new(TestIo { io, _peer: client }));
56    }
57}
58
59/// Create service that always responds with `HttpResponse::Ok()`
60pub fn ok_service<St: State, In>()
61-> impl Service<St, WebRequest<In>, Res = WebResponse, Error = std::convert::Infallible> {
62    default_service::<St, In>(StatusCode::OK)
63}
64
65/// Create service that responds with response with specified status code
66pub fn default_service<St: State, In>(
67    status_code: StatusCode,
68) -> impl Service<St, WebRequest<In>, Res = WebResponse, Error = Infallible> {
69    fn_service(async move |req: WebRequest<In>| {
70        Ok::<_, Infallible>(req.into_response(HttpResponse::builder(status_code).build()))
71    })
72}
73
74/// This method accepts application builder instance, and constructs
75/// service.
76///
77/// ```rust
78/// use ntex::service::Service;
79/// use ntex::http::StatusCode;
80/// use ntex::web::{self, test, App, HttpResponse};
81///
82/// #[ntex::test]
83/// async fn test_init_service() {
84///     let mut app = test::init_service(
85///         App::new()
86///             .service(web::resource("/test").to(async || { HttpResponse::Ok() }))
87///     ).await;
88///
89///     // Create request object
90///     let req = test::TestRequest::with_uri("/test").to_request();
91///
92///     // Execute application
93///     let resp = app.call(req).await.unwrap();
94///     assert_eq!(resp.status(), StatusCode::OK);
95/// }
96/// ```
97pub async fn init_service<R, S, E>(app: R) -> Pipeline<Request, Response, E>
98where
99    R: IntoServiceFactory<S, (), Request>,
100    S: ServiceFactory<(), Request, Res = Response, Error = E> + 'static,
101    S::InitError: fmt::Debug,
102{
103    let srv = app.into_factory().map_init_err(|e| log::error!("{e:?}"));
104    srv.pipeline(()).await.unwrap()
105}
106
107/// This method accepts application builder instance, and constructs
108/// service with specified state.
109///
110/// ```rust
111/// use ntex::service::Service;
112/// use ntex::http::StatusCode;
113/// use ntex::web::{self, test, App, HttpResponse};
114///
115/// #[ntex::test]
116/// async fn test_init_service() {
117///     let mut app = test::init_service_st(
118///         (),
119///         App::new()
120///             .service(web::resource("/test").to(async || { HttpResponse::Ok() }))
121///     ).await;
122///
123///     // Create request object
124///     let req = test::TestRequest::with_uri("/test").to_request();
125///
126///     // Execute application
127///     let resp = app.call(req).await.unwrap();
128///     assert_eq!(resp.status(), StatusCode::OK);
129/// }
130/// ```
131pub async fn init_service_st<St, R, S, E>(st: St, app: R) -> Pipeline<Request, Response, E>
132where
133    St: 'static,
134    R: IntoServiceFactory<S, St, Request>,
135    S: ServiceFactory<St, Request, Res = Response, Error = E> + 'static,
136    S::InitError: fmt::Debug,
137{
138    let srv = app.into_factory().map_init_err(|e| log::error!("{e:?}"));
139    srv.pipeline(st).await.unwrap()
140}
141
142/// Calls service and waits for response future completion.
143///
144/// ```rust
145/// use ntex::http::StatusCode;
146/// use ntex::web::{self, test, App, HttpResponse};
147///
148/// #[ntex::test]
149/// async fn test_response() {
150///     let mut app = test::init_service(
151///         App::new()
152///             .service(web::resource("/test").to(async || {
153///                 HttpResponse::Ok()
154///             }))
155///     ).await;
156///
157///     // Create request object
158///     let req = test::TestRequest::with_uri("/test").to_request();
159///
160///     // Call application
161///     let resp = test::call_service(&mut app, req).await;
162///     assert_eq!(resp.status(), StatusCode::OK);
163/// }
164/// ```
165pub async fn call_service<R, E>(app: &Pipeline<R, Response, E>, req: R) -> Response
166where
167    R: 'static,
168    E: fmt::Debug + 'static,
169{
170    app.call(req).await.unwrap()
171}
172
173/// Helper function that returns a response body of a `TestRequest`
174///
175/// ```rust
176/// use ntex::{http::header, util::Bytes};
177/// use ntex::web::{self, test, App, HttpResponse};
178///
179/// #[ntex::test]
180/// async fn test_index() {
181///     let mut app = test::init_service(
182///         App::new().service(
183///             web::resource("/index.html")
184///                 .route(web::post().to(async || {
185///                     HttpResponse::Ok().body("welcome!")
186///                 })))
187///     ).await;
188///
189///     let req = test::TestRequest::post()
190///         .uri("/index.html")
191///         .header(header::CONTENT_TYPE, "application/json")
192///         .to_request();
193///
194///     let result = test::read_response(&mut app, req).await;
195///     assert_eq!(result, Bytes::from_static(b"welcome!"));
196/// }
197/// ```
198pub async fn read_response<E>(app: &Pipeline<Request, Response, E>, req: Request) -> Bytes
199where
200    E: 'static,
201{
202    let mut resp = app
203        .call(req)
204        .await
205        .unwrap_or_else(|_| panic!("read_response failed at application call"));
206
207    let mut body = resp.take_body();
208    let mut bytes = BytesMut::new();
209    while let Some(item) = stream_recv(&mut body).await {
210        bytes.extend_from_slice(&item.unwrap());
211    }
212    bytes.freeze()
213}
214
215/// Helper function that returns a response body of a `WebResponse`
216///
217/// ```rust
218/// use ntex::{util::Bytes, http::header};
219/// use ntex::web::{self, test, App, HttpResponse};
220///
221/// #[ntex::test]
222/// async fn test_index() {
223///     let mut app = test::init_service(
224///         App::new().service(
225///             web::resource("/index.html")
226///                 .route(web::post().to(async || {
227///                     HttpResponse::Ok().body("welcome!")
228///                 })))
229///     ).await;
230///
231///     let req = test::TestRequest::post()
232///         .uri("/index.html")
233///         .header(header::CONTENT_TYPE, "application/json")
234///         .to_request();
235///
236///     let resp = test::call_service(&mut app, req).await;
237///     let result = test::read_body(resp).await;
238///     assert_eq!(result, Bytes::from_static(b"welcome!"));
239/// }
240/// ```
241pub async fn read_body(res: impl Into<Response>) -> Bytes {
242    let mut body = res.into().take_body();
243    let mut bytes = BytesMut::new();
244    while let Some(item) = stream_recv(&mut body).await {
245        bytes.extend_from_slice(&item.unwrap());
246    }
247    bytes.freeze()
248}
249
250/// Reads response's body and combines it to a Bytes objects
251pub async fn load_stream<S, E>(mut stream: S) -> Result<Bytes, E>
252where
253    S: Stream<Item = Result<Bytes, E>> + Unpin,
254{
255    let mut data = BytesMut::new();
256    while let Some(item) = stream_recv(&mut stream).await {
257        data.extend_from_slice(&item?);
258    }
259    Ok(data.freeze())
260}
261
262/// Helper function that returns a deserialized response body of a `TestRequest`
263///
264/// ```rust
265/// use ntex::http::header;
266/// use ntex::web::{self, test, App, HttpResponse};
267/// use serde::{Serialize, Deserialize};
268///
269/// #[derive(Serialize, Deserialize)]
270/// pub struct Person {
271///     id: String,
272///     name: String
273/// }
274///
275/// #[ntex::test]
276/// async fn test_add_person() {
277///     let mut app = test::init_service(
278///         App::new().service(
279///             web::resource("/people")
280///                 .route(web::post().to(async |person: web::types::Json<Person>| {
281///                     HttpResponse::Ok()
282///                         .json(&person.into_inner())})
283///                     ))
284///     ).await;
285///
286///     let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
287///
288///     let req = test::TestRequest::post()
289///         .uri("/people")
290///         .header(header::CONTENT_TYPE, "application/json")
291///         .payload(payload)
292///         .to_request();
293///
294///     let result: Person = test::read_response_json(&mut app, req).await;
295/// }
296/// ```
297pub async fn read_response_json<T, E>(app: &Pipeline<Request, Response, E>, req: Request) -> T
298where
299    T: DeserializeOwned,
300    E: 'static,
301{
302    let body = read_response(app, req).await;
303
304    serde_json::from_slice(&body)
305        .unwrap_or_else(|e| panic!("read_response_json failed during deserialization, {e:?}"))
306}
307
308/// Helper method for extractors testing
309pub async fn from_request<St, T: FromRequest<St>>(
310    st: &St,
311    req: &HttpRequest,
312    payload: &mut Payload,
313) -> Result<T, T::Error> {
314    T::from_request(st, req, payload).await
315}
316
317/// Helper method for responders testing
318pub async fn respond_to<T: Responder>(slf: T, req: &HttpRequest) -> HttpResponse {
319    T::respond_to(slf, &(), req).await
320}
321
322/// Test `Request` builder.
323///
324/// For unit testing, ntex provides a request builder type and a simple handler runner. `TestRequest` implements a builder-like pattern.
325/// You can generate various types of request via `TestRequest`'s methods:
326///  * `TestRequest::to_request` creates `ntex::http::Request` instance.
327///  * `TestRequest::to_srv_request` creates `WebRequest` instance, which is used for testing middlewares and chain adapters.
328///  * `TestRequest::to_srv_response` creates `WebResponse` instance.
329///  * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers.
330///
331/// ```rust
332/// use ntex::http::{header, StatusCode, HttpMessage};
333/// use ntex::web::{self, test, HttpRequest, HttpResponse};
334///
335/// async fn index(req: HttpRequest) -> HttpResponse {
336///     if req.headers().contains_key(header::CONTENT_TYPE) {
337///         HttpResponse::Ok().into()
338///     } else {
339///         HttpResponse::BadRequest().into()
340///     }
341/// }
342///
343/// #[ntex::test]
344/// async fn test_index() {
345///     let req = test::TestRequest::with_header("content-type", "text/plain")
346///         .to_http_request();
347///
348///     let resp = index(req).await;
349///     assert_eq!(resp.status(), StatusCode::OK);
350///
351///     let req = test::TestRequest::default().to_http_request();
352///     let resp = index(req).await;
353///     assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
354/// }
355/// ```
356#[derive(Debug)]
357pub struct TestRequest<St = ()> {
358    req: HttpTestRequest,
359    rmap: ResourceMap,
360    path: Path<Url>,
361    peer_addr: Option<SocketAddr>,
362    state: St,
363    config: WebAppConfig,
364}
365
366impl Default for TestRequest {
367    fn default() -> TestRequest {
368        TestRequest {
369            req: HttpTestRequest::default(),
370            rmap: ResourceMap::new(ResourceDef::new("")),
371            path: Path::new(Url::new()),
372            peer_addr: None,
373            state: (),
374            config: WebAppConfig::new(),
375        }
376    }
377}
378
379impl TestRequest {
380    #[must_use]
381    /// Create `TestRequest` and set request uri.
382    pub fn with_uri(path: &str) -> TestRequest {
383        TestRequest::default().uri(path)
384    }
385
386    #[must_use]
387    /// Create `TestRequest` and set header.
388    pub fn with_header<K, V>(key: K, value: V) -> TestRequest
389    where
390        HeaderName: TryFrom<K>,
391        HeaderValue: TryFrom<V>,
392        <HeaderName as TryFrom<K>>::Error: Into<HttpError>,
393    {
394        TestRequest::default().header(key, value)
395    }
396
397    #[must_use]
398    /// Create `TestRequest` and set method to `Method::GET`.
399    pub fn get() -> TestRequest {
400        TestRequest::default().method(Method::GET)
401    }
402
403    #[must_use]
404    /// Create `TestRequest` and set method to `Method::POST`.
405    pub fn post() -> TestRequest {
406        TestRequest::default().method(Method::POST)
407    }
408
409    #[must_use]
410    /// Create `TestRequest` and set method to `Method::PUT`.
411    pub fn put() -> TestRequest {
412        TestRequest::default().method(Method::PUT)
413    }
414
415    #[must_use]
416    /// Create `TestRequest` and set method to `Method::PATCH`.
417    pub fn patch() -> TestRequest {
418        TestRequest::default().method(Method::PATCH)
419    }
420
421    #[must_use]
422    /// Create `TestRequest` and set method to `Method::DELETE`.
423    pub fn delete() -> TestRequest {
424        TestRequest::default().method(Method::DELETE)
425    }
426}
427
428impl<St> TestRequest<St> {
429    #[must_use]
430    /// Set HTTP version of this request.
431    pub fn version(mut self, ver: Version) -> Self {
432        self.req.version(ver);
433        self
434    }
435
436    #[must_use]
437    /// Set HTTP method of this request.
438    pub fn method(mut self, meth: Method) -> Self {
439        self.req.method(meth);
440        self
441    }
442
443    #[must_use]
444    /// Set HTTP Uri of this request.
445    pub fn uri(mut self, path: &str) -> Self {
446        self.req.uri(path);
447        self
448    }
449
450    #[must_use]
451    /// Set a header.
452    pub fn header<K, V>(mut self, key: K, value: V) -> Self
453    where
454        HeaderName: TryFrom<K>,
455        HeaderValue: TryFrom<V>,
456        <HeaderName as TryFrom<K>>::Error: Into<HttpError>,
457    {
458        self.req.header(key, value);
459        self
460    }
461
462    #[must_use]
463    #[cfg(feature = "cookie")]
464    /// Set cookie for this request.
465    pub fn cookie<C>(mut self, cookie: C) -> Self
466    where
467        C: Into<Cookie<'static>>,
468    {
469        self.req.cookie(cookie);
470        self
471    }
472
473    #[must_use]
474    /// Set request path pattern parameter.
475    pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
476        self.path.add_static(name, value);
477        self
478    }
479
480    #[must_use]
481    /// Set peer addr.
482    ///
483    /// The address is returned by `peer_addr()` of the generated request.
484    /// Requests with a peer address must be created inside the ntex runtime.
485    pub fn peer_addr(mut self, addr: SocketAddr) -> Self {
486        self.peer_addr = Some(addr);
487        self
488    }
489
490    #[must_use]
491    /// Set request payload.
492    pub fn payload<B: Into<Bytes>>(mut self, data: B) -> Self {
493        self.req.set_payload(data);
494        self
495    }
496
497    #[must_use]
498    /// Serialize `data` to a URL encoded form and set it as the request payload.
499    ///
500    /// The `Content-Type` header is set to `application/x-www-form-urlencoded`.
501    pub fn form<T: Serialize>(mut self, data: &T) -> Self {
502        let bytes = serde_urlencoded::to_string(data)
503            .expect("Failed to serialize test data as a urlencoded form");
504        self.req.set_payload(bytes);
505        self.req
506            .header(CONTENT_TYPE, "application/x-www-form-urlencoded");
507        self
508    }
509
510    #[must_use]
511    /// Serialize `data` to JSON and set it as the request payload.
512    ///
513    /// The `Content-Type` header is set to `application/json`.
514    pub fn json<T: Serialize>(mut self, data: &T) -> Self {
515        let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json");
516        self.req.set_payload(bytes);
517        self.req.header(CONTENT_TYPE, "application/json");
518        self
519    }
520
521    #[must_use]
522    /// Set request state.
523    pub fn state<NewSt: 'static>(self, state: NewSt) -> TestRequest<NewSt> {
524        TestRequest {
525            state,
526            req: self.req,
527            rmap: self.rmap,
528            path: self.path,
529            peer_addr: self.peer_addr,
530            config: self.config,
531        }
532    }
533
534    #[must_use]
535    /// Set application state.
536    ///
537    /// This is equivalent of [`WebAppConfig::set_state()`] method for testing
538    /// purpose. The value is available via `HttpRequest::app_state()`.
539    pub fn app_state<T: Send + Sync + 'static>(mut self, data: T) -> Self {
540        self.config = self.config.set_state(data);
541        self
542    }
543
544    #[must_use]
545    #[cfg(test)]
546    /// Set request config
547    pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self {
548        self.rmap = rmap;
549        self
550    }
551
552    #[must_use]
553    /// Complete request creation and generate `Request` instance.
554    pub fn to_request(mut self) -> Request {
555        self.req.build()
556    }
557
558    #[must_use]
559    /// Complete request creation and generate `WebRequest` instance.
560    pub fn to_srv_request(mut self) -> WebRequest<St> {
561        let (mut head, payload) = self.req.build().into_parts();
562        set_peer_addr(&mut head, self.peer_addr);
563        *self.path.get_mut() = head.uri.clone();
564        let cfg = SharedCfg::new("TEST").add(self.config).build();
565
566        WebRequest::new(
567            HttpRequest::new(self.path, head, Rc::new(self.rmap), cfg.get()),
568            payload,
569            self.state,
570        )
571    }
572
573    #[must_use]
574    /// Complete request creation and generate `WebResponse` instance.
575    pub fn to_srv_response(self, res: HttpResponse) -> WebResponse {
576        self.to_srv_request().into_response(res)
577    }
578
579    #[must_use]
580    /// Complete request creation and generate `HttpRequest` instance.
581    pub fn to_http_request(mut self) -> HttpRequest {
582        let (mut head, _) = self.req.build().into_parts();
583        set_peer_addr(&mut head, self.peer_addr);
584        *self.path.get_mut() = head.uri.clone();
585        let cfg = SharedCfg::new("TEST").add(self.config).build();
586
587        HttpRequest::new(self.path, head, Rc::new(self.rmap), cfg.get())
588    }
589
590    #[must_use]
591    /// Complete request creation and generate `HttpRequest` and `Payload` instances.
592    pub fn to_http_parts(mut self) -> (HttpRequest, Payload, St) {
593        let (mut head, payload) = self.req.build().into_parts();
594        set_peer_addr(&mut head, self.peer_addr);
595        *self.path.get_mut() = head.uri.clone();
596        let cfg = SharedCfg::new("TEST").add(self.config).build();
597
598        let req = HttpRequest::new(self.path, head, Rc::new(self.rmap), cfg.get());
599
600        (req, payload, self.state)
601    }
602}
603
604/// Start test server with default configuration
605///
606/// Test server is very simple server that simplify process of writing
607/// integration tests cases for ntex web applications.
608///
609/// # Examples
610///
611/// ```rust
612/// use ntex::web::{self, test, App, HttpResponse};
613///
614/// async fn my_handler() -> Result<HttpResponse, std::io::Error> {
615///     Ok(HttpResponse::Ok().into())
616/// }
617///
618/// #[ntex::test]
619/// async fn test_example() {
620///     let srv = test::server(
621///         async |_| App::new().service(
622///                 web::resource("/").to(my_handler))
623///     );
624///
625///     let req = srv.get("/");
626///     let response = req.send().await.unwrap();
627///     assert!(response.status().is_success());
628/// }
629/// ```
630pub fn server<F, I, Sf>(factory: F) -> TestServer
631where
632    F: AsyncFn(&()) -> I + Send + Clone + 'static,
633    I: IntoServiceFactory<Sf, (), Request>,
634    Sf: ServiceFactory<(), Request> + 'static,
635    Sf::Res: Into<Response>,
636    Sf::Error: ResponseError,
637    Sf::InitError: fmt::Debug,
638{
639    server_with(TestServerConfig::default(), factory)
640}
641
642/// Start test server with custom configuration
643///
644/// Test server could be configured in different ways, for details check
645/// `TestServerConfig` docs.
646///
647/// # Examples
648///
649/// ```rust
650/// use ntex::web::{self, test, App, HttpResponse};
651///
652/// async fn my_handler() -> HttpResponse {
653///     HttpResponse::Ok().into()
654/// }
655///
656/// #[ntex::test]
657/// async fn test_example() {
658///     let srv = test::server_with(test::config().h1(), async |_|
659///         App::new().service(web::resource("/").to(my_handler))
660///     );
661///
662///     let req = srv.get("/");
663///     let response = req.send().await.unwrap();
664///     assert!(response.status().is_success());
665/// }
666/// ```
667pub fn server_with<F, I, Sf>(cfg: TestServerConfig, factory: F) -> TestServer
668where
669    F: AsyncFn(&()) -> I + Send + Clone + 'static,
670    I: IntoServiceFactory<Sf, (), Request>,
671    Sf: ServiceFactory<(), Request> + 'static,
672    Sf::Res: Into<Response>,
673    Sf::Error: ResponseError + 'static,
674    Sf::InitError: fmt::Debug,
675{
676    // Type-erase the application factory, so the HTTP server stacks below
677    // are compiled once instead of once per test application.
678    let factory = Mutex::new(factory);
679    let factory: AppFactory = Arc::new(move || {
680        let factory = factory.lock().unwrap().clone();
681        Box::pin(async move {
682            boxed::factory(
683                factory(&())
684                    .await
685                    .into_factory()
686                    .map(Into::into)
687                    .map_err(AppError::new)
688                    .map_init_err(|e| io::Error::other(format!("{e:?}"))),
689            )
690        })
691    });
692    start_server(cfg, factory)
693}
694
695type AppFactory = Arc<
696    dyn Fn() -> Pin<
697            Box<dyn Future<Output = BoxServiceFactory<(), Request, Response, AppError, io::Error>>>,
698        > + Send
699        + Sync,
700>;
701
702/// Type-erased application error
703struct AppError(Box<dyn ResponseError>);
704
705impl AppError {
706    fn new<E: ResponseError + 'static>(err: E) -> Self {
707        AppError(Box::new(err))
708    }
709}
710
711impl fmt::Debug for AppError {
712    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
713        fmt::Debug::fmt(&self.0, f)
714    }
715}
716
717impl fmt::Display for AppError {
718    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
719        fmt::Display::fmt(&self.0, f)
720    }
721}
722
723impl std::error::Error for AppError {
724    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
725        self.0.source()
726    }
727}
728
729impl ResponseError for AppError {
730    fn error_response(&self) -> Response {
731        self.0.error_response()
732    }
733}
734
735fn start_server(cfg: TestServerConfig, factory: AppFactory) -> TestServer {
736    let sys = System::current().config();
737    let name = System::current().name().to_string();
738
739    let id = Uuid::now_v7();
740    let (tx, rx) = mpsc::channel();
741    log::debug!("Starting {name:?} web server {id:?}");
742
743    let ssl = match cfg.stream {
744        StreamType::Tcp => false,
745        #[cfg(feature = "openssl")]
746        StreamType::Openssl(_) => true,
747        #[cfg(feature = "rustls")]
748        StreamType::Rustls(_) => true,
749    };
750
751    // run server in separate thread
752    thread::spawn(move || {
753        let sys = System::with_config(&name, sys);
754        let factory = async move |&(): &()| factory().await;
755
756        let ctimeout = cfg.client_timeout;
757        let port = cfg.port;
758        let tcp = cfg
759            .listener
760            .unwrap_or_else(|| net::TcpListener::bind(format!("127.0.0.1:{port}")).unwrap());
761        let local_addr = tcp.local_addr().unwrap();
762
763        sys.run(move || {
764            let builder = crate::server::build().workers(1).disable_signals();
765            let secure = match cfg.stream {
766                StreamType::Tcp => false,
767                #[cfg(feature = "openssl")]
768                StreamType::Openssl(_) => true,
769                #[cfg(feature = "rustls")]
770                StreamType::Rustls(_) => true,
771            };
772            let c = cfg.srv_cfg.clone().unwrap_or_else(|| {
773                SharedCfg::new("WEB-SRV")
774                    .add(IoConfig::new())
775                    .add(http::HttpServiceConfig::new().set_headers_read_rate(
776                        ctimeout,
777                        Seconds::ZERO,
778                        256,
779                    ))
780                    .add(WebAppConfig::with(
781                        &name,
782                        secure,
783                        local_addr,
784                        format!("{local_addr}"),
785                    ))
786                    .into()
787            });
788
789            let srv = match cfg.stream {
790                StreamType::Tcp => match cfg.tp {
791                    HttpVer::Http1 => builder.listen("test", tcp, c, async move |st| {
792                        HttpService::h1(factory(st).await)
793                    }),
794                    HttpVer::Http2 => builder.listen("test", tcp, c, async move |st| {
795                        HttpService::h2(factory(st).await)
796                    }),
797                    HttpVer::Both => builder.listen("test", tcp, c, async move |st| {
798                        HttpService::new(factory(st).await)
799                    }),
800                },
801                #[cfg(feature = "openssl")]
802                StreamType::Openssl(acceptor) => match cfg.tp {
803                    HttpVer::Http1 => builder.listen("test", tcp, c, async move |st| {
804                        http::openssl(acceptor.clone(), HttpService::h1(factory(st).await))
805                    }),
806                    HttpVer::Http2 => builder.listen("test", tcp, c, async move |st| {
807                        http::openssl(acceptor.clone(), HttpService::h2(factory(st).await))
808                    }),
809                    HttpVer::Both => builder.listen("test", tcp, c, async move |st| {
810                        http::openssl(acceptor.clone(), HttpService::new(factory(st).await))
811                    }),
812                },
813                #[cfg(feature = "rustls")]
814                StreamType::Rustls(config) => match cfg.tp {
815                    HttpVer::Http1 => builder.listen("test", tcp, c, async move |st| {
816                        http::rustls(
817                            config.clone(),
818                            http::ALPN_PROTO_H1,
819                            HttpService::h1(factory(st).await),
820                        )
821                    }),
822                    HttpVer::Http2 => builder.listen("test", tcp, c, async move |st| {
823                        http::rustls(
824                            config.clone(),
825                            http::ALPN_PROTO_H2,
826                            HttpService::h2(factory(st).await),
827                        )
828                    }),
829                    HttpVer::Both => builder.listen("test", tcp, c, async move |st| {
830                        http::rustls(
831                            config.clone(),
832                            http::ALPN_PROTOS,
833                            HttpService::new(factory(st).await),
834                        )
835                    }),
836                },
837            }
838            .unwrap()
839            .run();
840
841            tx.send((System::current(), srv, local_addr)).unwrap();
842            Ok(())
843        })
844    });
845    let (system, server, addr) = rx.recv().unwrap();
846    thread::sleep(Millis(25).into());
847
848    let cfg = cfg.client_cfg.clone().unwrap_or_else(|| {
849        SharedCfg::new("TEST-CLIENT")
850            .add(IoConfig::new().set_connect_timeout(Millis(90_000)))
851            .add(ntex_tls::TlsConfig::new().set_handshake_timeout(Seconds(5)))
852            .add(
853                ntex_h2::ServiceConfig::new()
854                    .set_max_header_list_size(256 * 1024)
855                    .set_max_header_continuation_frames(96),
856            )
857            .add(
858                ClientConfig::new()
859                    .set_response_timeout(Seconds(30))
860                    .set_response_payload_timeout(Seconds(30)),
861            )
862            .add(
863                WsClientConfig::new()
864                    .set_address(addr)
865                    .set_handshake_timeout(Seconds(60)),
866            )
867            .build()
868    });
869
870    let client = {
871        #[cfg(feature = "openssl")]
872        {
873            use tls_openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
874
875            let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
876            builder.set_verify(SslVerifyMode::NONE);
877            let _ = builder
878                .set_alpn_protos(b"\x02h2\x08http/1.1")
879                .map_err(|e| log::error!("Cannot set alpn protocol: {e:?}"));
880            Client::builder()
881                .openssl(builder.build())
882                .build(cfg.clone())
883        }
884        #[cfg(not(feature = "openssl"))]
885        {
886            Client::builder().build(cfg.clone())
887        }
888    };
889
890    TestServer {
891        id,
892        cfg,
893        addr,
894        client,
895        system,
896        ssl,
897        server,
898    }
899}
900
901#[derive(Debug)]
902/// Test server configuration
903pub struct TestServerConfig {
904    tp: HttpVer,
905    stream: StreamType,
906    client_timeout: Seconds,
907    port: u16,
908    listener: Option<net::TcpListener>,
909    srv_cfg: Option<SharedCfg>,
910    client_cfg: Option<SharedCfg>,
911}
912
913#[derive(Clone, Debug)]
914enum HttpVer {
915    Http1,
916    Http2,
917    Both,
918}
919
920#[derive(Clone)]
921#[allow(clippy::large_enum_variant)]
922enum StreamType {
923    Tcp,
924    #[cfg(feature = "openssl")]
925    Openssl(tls_openssl::ssl::SslAcceptor),
926    #[cfg(feature = "rustls")]
927    Rustls(tls_rustls::ServerConfig),
928}
929
930impl fmt::Debug for StreamType {
931    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
932        match self {
933            StreamType::Tcp => write!(f, "StreamType::Tcp"),
934            #[cfg(feature = "openssl")]
935            StreamType::Openssl(_) => write!(f, "StreamType::Openssl"),
936            #[cfg(feature = "rustls")]
937            StreamType::Rustls(_) => write!(f, "StreamType::Rustls"),
938        }
939    }
940}
941
942impl Default for TestServerConfig {
943    fn default() -> Self {
944        TestServerConfig::new()
945    }
946}
947
948#[must_use]
949/// Create default test server config
950pub fn config() -> TestServerConfig {
951    TestServerConfig::new()
952}
953
954impl TestServerConfig {
955    #[must_use]
956    /// Create default server configuration
957    pub(crate) fn new() -> TestServerConfig {
958        TestServerConfig {
959            tp: HttpVer::Both,
960            stream: StreamType::Tcp,
961            client_timeout: Seconds(5),
962            port: 0,
963            listener: None,
964            srv_cfg: None,
965            client_cfg: None,
966        }
967    }
968
969    #[must_use]
970    /// Start http/1.1 server only.
971    pub fn h1(mut self) -> Self {
972        self.tp = HttpVer::Http1;
973        self
974    }
975
976    #[must_use]
977    /// Start http/2 server only.
978    pub fn h2(mut self) -> Self {
979        self.tp = HttpVer::Http2;
980        self
981    }
982
983    #[must_use]
984    /// Start openssl server.
985    #[cfg(feature = "openssl")]
986    pub fn openssl(mut self, acceptor: tls_openssl::ssl::SslAcceptor) -> Self {
987        self.stream = StreamType::Openssl(acceptor);
988        self
989    }
990
991    #[must_use]
992    /// Start rustls server.
993    #[cfg(feature = "rustls")]
994    pub fn rustls(mut self, config: tls_rustls::ServerConfig) -> Self {
995        self.stream = StreamType::Rustls(config);
996        self
997    }
998
999    #[must_use]
1000    /// Set server client timeout in seconds for first request.
1001    pub fn client_timeout(mut self, val: Seconds) -> Self {
1002        self.client_timeout = val;
1003        self
1004    }
1005
1006    #[must_use]
1007    /// Set server port. By default, test server binds to a random port assigned by OS.
1008    pub fn port(mut self, port: u16) -> Self {
1009        self.port = port;
1010        self
1011    }
1012
1013    #[must_use]
1014    /// Use an already bound TCP listener for the test server.
1015    ///
1016    /// When set, the configured port is ignored.
1017    pub fn listener(mut self, listener: net::TcpListener) -> Self {
1018        self.listener = Some(listener);
1019        self
1020    }
1021
1022    #[must_use]
1023    /// Use custom `SharedCfg` for test server.
1024    pub fn server_cfg(mut self, cfg: impl Into<SharedCfg>) -> Self {
1025        self.srv_cfg = Some(cfg.into());
1026        self
1027    }
1028
1029    #[must_use]
1030    /// Use custom `SharedCfg` for client.
1031    pub fn client_cfg(mut self, cfg: impl Into<SharedCfg>) -> Self {
1032        self.client_cfg = Some(cfg.into());
1033        self
1034    }
1035}
1036
1037#[derive(Debug)]
1038/// Test server controller
1039pub struct TestServer {
1040    id: Uuid,
1041    cfg: SharedCfg,
1042    addr: net::SocketAddr,
1043    client: Client,
1044    system: crate::rt::System,
1045    ssl: bool,
1046    server: Server,
1047}
1048
1049impl TestServer {
1050    /// Returns the test server's socket address.
1051    pub fn addr(&self) -> net::SocketAddr {
1052        self.addr
1053    }
1054
1055    /// Construct test server url
1056    pub fn url(&self, uri: &str) -> String {
1057        let scheme = if self.ssl { "https" } else { "http" };
1058
1059        if uri.starts_with('/') {
1060            format!("{}://localhost:{}{}", scheme, self.addr.port(), uri)
1061        } else {
1062            format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri)
1063        }
1064    }
1065
1066    /// Create `GET` request
1067    pub fn get<S: AsRef<str>>(&self, path: S) -> ClientRequest {
1068        self.client.get(self.url(path.as_ref()).as_str())
1069    }
1070
1071    /// Create `POST` request
1072    pub fn post<S: AsRef<str>>(&self, path: S) -> ClientRequest {
1073        self.client.post(self.url(path.as_ref()).as_str())
1074    }
1075
1076    /// Create `HEAD` request
1077    pub fn head<S: AsRef<str>>(&self, path: S) -> ClientRequest {
1078        self.client.head(self.url(path.as_ref()).as_str())
1079    }
1080
1081    /// Create `PUT` request
1082    pub fn put<S: AsRef<str>>(&self, path: S) -> ClientRequest {
1083        self.client.put(self.url(path.as_ref()).as_str())
1084    }
1085
1086    /// Create `PATCH` request
1087    pub fn patch<S: AsRef<str>>(&self, path: S) -> ClientRequest {
1088        self.client.patch(self.url(path.as_ref()).as_str())
1089    }
1090
1091    /// Create `DELETE` request
1092    pub fn delete<S: AsRef<str>>(&self, path: S) -> ClientRequest {
1093        self.client.delete(self.url(path.as_ref()).as_str())
1094    }
1095
1096    /// Create `OPTIONS` request
1097    pub fn options<S: AsRef<str>>(&self, path: S) -> ClientRequest {
1098        self.client.options(self.url(path.as_ref()).as_str())
1099    }
1100
1101    /// Create `QUERY` request
1102    pub fn query<S: AsRef<str>>(&self, path: S) -> ClientRequest {
1103        self.client.query(self.url(path.as_ref()).as_str())
1104    }
1105
1106    /// Create client request with the specified method
1107    ///
1108    /// A relative `path` is resolved against the test server url, an absolute
1109    /// url (containing `://`) is used as is.
1110    pub fn request<S: AsRef<str>>(&self, method: Method, path: S) -> ClientRequest {
1111        let path = path.as_ref();
1112        if path.contains("://") {
1113            self.client.request(method, path)
1114        } else {
1115            self.client.request(method, self.url(path).as_str())
1116        }
1117    }
1118
1119    /// Load response's body
1120    pub async fn load_body(
1121        &self,
1122        response: ClientResponse,
1123    ) -> Result<Bytes, Error<ClientPayloadError>> {
1124        response.body().limit(10_485_760).await
1125    }
1126
1127    #[cfg(feature = "ws")]
1128    /// Connect to websocket server at a given path
1129    pub async fn ws_at(&self, path: &str) -> Result<WsConnection<Sealed>, Error<WsClientError>> {
1130        if self.ssl {
1131            #[cfg(feature = "openssl")]
1132            {
1133                use tls_openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
1134
1135                let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
1136                builder.set_verify(SslVerifyMode::NONE);
1137                let _ = builder
1138                    .set_alpn_protos(b"\x08http/1.1")
1139                    .map_err(|e| log::error!("Cannot set alpn protocol: {e:?}"));
1140
1141                WsClient::new(self.url(path), &self.cfg)
1142                    .openssl(builder.build())
1143                    .connect()
1144                    .await
1145                    .map(WsConnection::seal)
1146            }
1147            #[cfg(not(feature = "openssl"))]
1148            {
1149                panic!("openssl feature is required")
1150            }
1151        } else {
1152            WsClient::new(self.url(path), &self.cfg)
1153                .connect()
1154                .await
1155                .map(WsConnection::seal)
1156        }
1157    }
1158
1159    #[cfg(feature = "ws")]
1160    /// Connect to a websocket server
1161    pub async fn ws(&self) -> Result<WsConnection<Sealed>, Error<WsClientError>> {
1162        self.ws_at("/").await
1163    }
1164
1165    /// Gracefully stop http server
1166    pub async fn stop(&self) {
1167        self.server.stop(true).await;
1168    }
1169}
1170
1171impl Drop for TestServer {
1172    fn drop(&mut self) {
1173        log::debug!("Stopping test web server {:?}", self.id);
1174        drop(self.server.stop(false));
1175        thread::sleep(time::Duration::from_millis(75));
1176        self.system.stop();
1177        thread::sleep(time::Duration::from_millis(25));
1178    }
1179}
1180
1181#[cfg(test)]
1182mod tests {
1183    use serde::{Deserialize, Serialize};
1184
1185    use super::*;
1186    use crate::http::{HttpMessage, header};
1187    use crate::web::{self, App};
1188
1189    #[crate::rt_test]
1190    async fn test_basics() {
1191        let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json")
1192            .version(Version::HTTP_2)
1193            .header(header::DATE, "some date")
1194            .param("test", "123")
1195            .app_state(20u64)
1196            .peer_addr("127.0.0.1:8081".parse().unwrap())
1197            .to_http_request();
1198        assert!(req.headers().contains_key(header::CONTENT_TYPE));
1199        assert!(req.headers().contains_key(header::DATE));
1200        assert_eq!(req.peer_addr(), Some("127.0.0.1:8081".parse().unwrap()));
1201        assert_eq!(&req.match_info()["test"], "123");
1202        assert_eq!(req.version(), Version::HTTP_2);
1203        let data = req.app_state::<u64>().unwrap();
1204        assert_eq!(*data, 20);
1205        assert_eq!(format!("{:?}", StreamType::Tcp), "StreamType::Tcp");
1206
1207        let (_, pl, ()) = TestRequest::with_header(header::CONTENT_TYPE, "application/json")
1208            .to_srv_request()
1209            .into_parts();
1210        let res = load_stream(pl).await.unwrap();
1211        assert_eq!(res, &b""[..]);
1212    }
1213
1214    #[crate::rt_test]
1215    async fn test_request_methods() {
1216        let app = init_service(
1217            App::new().service(
1218                web::resource("/index.html")
1219                    .route(web::put().to(async || HttpResponse::Ok().body("put!")))
1220                    .route(web::patch().to(async || HttpResponse::Ok().body("patch!")))
1221                    .route(web::delete().to(async || HttpResponse::Ok().body("delete!"))),
1222            ),
1223        )
1224        .await;
1225
1226        let put_req = TestRequest::put()
1227            .uri("/index.html")
1228            .header(header::CONTENT_TYPE, "application/json")
1229            .to_request();
1230
1231        let result = read_response(&app, put_req).await;
1232        assert_eq!(result, Bytes::from_static(b"put!"));
1233
1234        let patch_req = TestRequest::patch()
1235            .uri("/index.html")
1236            .header(header::CONTENT_TYPE, "application/json")
1237            .to_request();
1238
1239        let result = read_response(&app, patch_req).await;
1240        assert_eq!(result, Bytes::from_static(b"patch!"));
1241
1242        let delete_req = TestRequest::delete().uri("/index.html").to_request();
1243        let result = read_response(&app, delete_req).await;
1244        assert_eq!(result, Bytes::from_static(b"delete!"));
1245    }
1246
1247    #[crate::rt_test]
1248    async fn test_response() {
1249        let app = init_service(
1250            App::new().service(
1251                web::resource("/index.html")
1252                    .route(web::post().to(async || HttpResponse::Ok().body("welcome!"))),
1253            ),
1254        )
1255        .await;
1256
1257        let req = TestRequest::post()
1258            .uri("/index.html")
1259            .header(header::CONTENT_TYPE, "application/json")
1260            .to_request();
1261
1262        let result = read_response(&app, req).await;
1263        assert_eq!(result, Bytes::from_static(b"welcome!"));
1264    }
1265
1266    #[derive(Serialize, Deserialize)]
1267    struct Person {
1268        id: String,
1269        name: String,
1270    }
1271
1272    #[crate::rt_test]
1273    async fn test_response_json() {
1274        let app = init_service(
1275            App::new().service(web::resource("/people").route(web::post().to(
1276                async |person: web::types::Json<Person>| {
1277                    HttpResponse::Ok().json(&person.into_inner())
1278                },
1279            ))),
1280        )
1281        .await;
1282
1283        let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
1284
1285        let req = TestRequest::post()
1286            .uri("/people")
1287            .header(header::CONTENT_TYPE, "application/json")
1288            .payload(payload)
1289            .to_request();
1290
1291        let result: Person = read_response_json(&app, req).await;
1292        assert_eq!(&result.id, "12345");
1293    }
1294
1295    #[crate::rt_test]
1296    async fn test_request_response_form() {
1297        let app = init_service(
1298            App::new().service(web::resource("/people").route(web::post().to(
1299                async |person: web::types::Form<Person>| {
1300                    HttpResponse::Ok().json(&person.into_inner())
1301                },
1302            ))),
1303        )
1304        .await;
1305
1306        let payload = Person {
1307            id: "12345".to_string(),
1308            name: "User name".to_string(),
1309        };
1310
1311        let req = TestRequest::post()
1312            .uri("/people")
1313            .form(&payload)
1314            .to_request();
1315
1316        assert_eq!(req.content_type(), "application/x-www-form-urlencoded");
1317
1318        let result: Person = read_response_json(&app, req).await;
1319        assert_eq!(&result.id, "12345");
1320        assert_eq!(&result.name, "User name");
1321    }
1322
1323    #[crate::rt_test]
1324    async fn test_request_response_json() {
1325        let app = init_service(
1326            App::new().service(web::resource("/people").route(web::post().to(
1327                async |person: web::types::Json<Person>| {
1328                    HttpResponse::Ok().json(&person.into_inner())
1329                },
1330            ))),
1331        )
1332        .await;
1333
1334        let payload = Person {
1335            id: "12345".to_string(),
1336            name: "User name".to_string(),
1337        };
1338
1339        let req = TestRequest::post()
1340            .uri("/people")
1341            .json(&payload)
1342            .to_request();
1343
1344        assert_eq!(req.content_type(), "application/json");
1345
1346        let result: Person = read_response_json(&app, req).await;
1347        assert_eq!(&result.id, "12345");
1348        assert_eq!(&result.name, "User name");
1349    }
1350
1351    #[crate::rt_test]
1352    async fn test_async_with_block() {
1353        async fn async_with_block() -> Result<HttpResponse, Infallible> {
1354            let res = web::block(move || Some(4usize).ok_or("wrong")).await;
1355
1356            #[allow(clippy::match_wild_err_arm)]
1357            match res {
1358                Ok(value) => Ok(HttpResponse::Ok()
1359                    .content_type("text/plain")
1360                    .body(format!("Async with block value: {value}"))),
1361                Err(_) => panic!("Unexpected"),
1362            }
1363        }
1364
1365        let app =
1366            init_service(App::new().service(web::resource("/index.html").to(async_with_block)))
1367                .await;
1368
1369        let req = TestRequest::post().uri("/index.html").to_request();
1370        let res = app.call(req).await.unwrap();
1371        assert!(res.status().is_success());
1372    }
1373
1374    #[crate::rt_test]
1375    async fn test_test_methods() {
1376        let srv = server(async |()| {
1377            App::new().service(
1378                web::resource("/").route((
1379                    web::route()
1380                        .method(Method::PUT)
1381                        .to(async || HttpResponse::Ok()),
1382                    web::route()
1383                        .method(Method::PATCH)
1384                        .to(async || HttpResponse::Ok()),
1385                    web::route()
1386                        .method(Method::DELETE)
1387                        .to(async || HttpResponse::Ok()),
1388                    web::route()
1389                        .method(Method::OPTIONS)
1390                        .to(async || HttpResponse::Ok()),
1391                )),
1392            )
1393        });
1394
1395        assert_eq!(srv.put("/").send().await.unwrap().status(), StatusCode::OK);
1396        assert_eq!(
1397            srv.patch("/").send().await.unwrap().status(),
1398            StatusCode::OK
1399        );
1400        assert_eq!(
1401            srv.delete("/").send().await.unwrap().status(),
1402            StatusCode::OK
1403        );
1404        assert_eq!(
1405            srv.options("/").send().await.unwrap().status(),
1406            StatusCode::OK
1407        );
1408
1409        let res = srv.put("").send().await.unwrap();
1410        assert_eq!(srv.load_body(res).await.unwrap(), Bytes::new());
1411    }
1412
1413    #[crate::rt_test]
1414    async fn test_request_state() {
1415        let (_, _, st) = TestRequest::default().state(10usize).to_http_parts();
1416        assert_eq!(st, 10);
1417
1418        let req = TestRequest::default()
1419            .state(web::AppState::new(5usize))
1420            .to_srv_request();
1421        assert_eq!(**req.st(), 5);
1422    }
1423
1424    #[crate::rt_test]
1425    async fn test_server_config() {
1426        let cfg = config()
1427            .h1()
1428            .port(0)
1429            .client_cfg(SharedCfg::new("CUSTOM-CLIENT"));
1430        let dbg = format!("{cfg:?}");
1431        assert!(dbg.contains("StreamType::Tcp"), "{dbg}");
1432
1433        let srv = server_with(cfg, async |()| {
1434            App::new().service(web::resource("/").to(async || HttpResponse::Ok()))
1435        });
1436        let res = srv.get("/").send().await.unwrap();
1437        assert_eq!(res.status(), StatusCode::OK);
1438        assert_eq!(res.version(), http::Version::HTTP_11);
1439        srv.stop().await;
1440    }
1441
1442    #[crate::rt_test]
1443    async fn test_server_listener_ignores_port() {
1444        // regression: the configured port was bound even if a listener is set
1445        let lst = net::TcpListener::bind("127.0.0.1:0").unwrap();
1446        let addr = lst.local_addr().unwrap();
1447
1448        let srv = server_with(config().port(addr.port()).listener(lst), async |()| {
1449            App::new().service(web::resource("/").to(async || HttpResponse::Ok()))
1450        });
1451        assert_eq!(srv.addr(), addr);
1452        let res = srv.get("/").send().await.unwrap();
1453        assert_eq!(res.status(), StatusCode::OK);
1454    }
1455
1456    #[cfg(feature = "openssl")]
1457    fn ssl_acceptor(h2: bool) -> tls_openssl::ssl::SslAcceptor {
1458        use tls_openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
1459
1460        let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
1461        builder
1462            .set_private_key_file("./tests/key.pem", SslFiletype::PEM)
1463            .unwrap();
1464        builder
1465            .set_certificate_chain_file("./tests/cert.pem")
1466            .unwrap();
1467        builder.set_alpn_select_callback(move |_, protos| {
1468            if h2 && protos.windows(3).any(|w| w == b"\x02h2") {
1469                Ok(b"h2")
1470            } else {
1471                Ok(b"http/1.1")
1472            }
1473        });
1474        builder.build()
1475    }
1476
1477    #[cfg(feature = "openssl")]
1478    #[crate::rt_test]
1479    async fn test_server_openssl() {
1480        for (cfg, h2, version) in [
1481            (config().h1(), false, http::Version::HTTP_11),
1482            (config().h2(), true, http::Version::HTTP_2),
1483            (config(), true, http::Version::HTTP_2),
1484            (config(), false, http::Version::HTTP_11),
1485        ] {
1486            let cfg = cfg.openssl(ssl_acceptor(h2));
1487            assert!(format!("{cfg:?}").contains("StreamType::Openssl"));
1488
1489            let srv = server_with(cfg, async |()| {
1490                App::new().service(web::resource("/").to(async || HttpResponse::Ok()))
1491            });
1492            assert!(srv.url("/").starts_with("https://"));
1493            let res = srv.get("/").send().await.unwrap();
1494            assert_eq!(res.status(), StatusCode::OK);
1495            assert_eq!(res.version(), version);
1496        }
1497    }
1498
1499    #[cfg(all(feature = "openssl", feature = "ws"))]
1500    #[crate::rt_test]
1501    async fn test_server_openssl_ws() {
1502        let srv = server_with(config().h1().openssl(ssl_acceptor(false)), async |()| {
1503            App::new().service(
1504                web::resource("/").route(web::to(async |req: web::HttpRequest| {
1505                    let _ = web::ws::start(&req, None, async |_: web::ws::Frame| {
1506                        Ok::<_, io::Error>(None)
1507                    })
1508                    .await;
1509                })),
1510            )
1511        });
1512        assert!(srv.ws().await.is_ok());
1513    }
1514
1515    #[cfg(feature = "rustls")]
1516    #[test]
1517    fn test_server_config_rustls_debug() {
1518        use std::{fs::File, io::BufReader};
1519
1520        let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
1521        let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap());
1522        let cert_chain = rustls_pemfile::certs(cert_file)
1523            .map(|r| r.unwrap())
1524            .collect();
1525        let key = rustls_pemfile::private_key(key_file).unwrap().unwrap();
1526        let cfg = tls_rustls::ServerConfig::builder()
1527            .with_no_client_auth()
1528            .with_single_cert(cert_chain, key)
1529            .unwrap();
1530        assert!(format!("{:?}", config().rustls(cfg)).contains("StreamType::Rustls"));
1531    }
1532
1533    #[cfg(feature = "cookie")]
1534    #[test]
1535    fn test_response_cookies() {
1536        let req = TestRequest::default()
1537            .cookie(
1538                coo_kie::Cookie::build(("name", "value"))
1539                    .domain("www.rust-lang.org")
1540                    .path("/test")
1541                    .http_only(true)
1542                    .max_age(::time::Duration::days(1)),
1543            )
1544            .to_http_request();
1545
1546        let cookies = req.cookies().unwrap();
1547        assert_eq!(cookies.len(), 1);
1548        assert_eq!(cookies[0].name(), "name");
1549    }
1550}