ntex_server/net/
test.rs

1//! Test server
2use std::{fmt, io, marker::PhantomData, net, thread};
3
4use ntex_io::Io;
5use ntex_net::tcp_connect;
6use ntex_rt::System;
7use ntex_service::{ServiceFactory, cfg::SharedCfg};
8use socket2::{Domain, SockAddr, Socket, Type};
9
10use super::{Server, ServerBuilder};
11
12/// Test server builder
13pub struct TestServerBuilder<F, R> {
14    factory: F,
15    config: SharedCfg,
16    client_config: SharedCfg,
17    _t: PhantomData<R>,
18}
19
20impl<F, R> fmt::Debug for TestServerBuilder<F, R> {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        f.debug_struct("TestServerBuilder")
23            .field("config", &self.config)
24            .field("client_config", &self.client_config)
25            .finish()
26    }
27}
28
29impl<F, R> TestServerBuilder<F, R>
30where
31    F: AsyncFn() -> R + Send + Clone + 'static,
32    R: ServiceFactory<Io, SharedCfg> + 'static,
33{
34    pub fn new(factory: F) -> Self {
35        Self {
36            factory,
37            config: SharedCfg::new("TEST-SERVER").into(),
38            client_config: SharedCfg::new("TEST-CLIENT").into(),
39            _t: PhantomData,
40        }
41    }
42
43    /// Set server io configuration
44    pub fn config<T: Into<SharedCfg>>(mut self, cfg: T) -> Self {
45        self.config = cfg.into();
46        self
47    }
48
49    /// Set client io configuration
50    pub fn client_config<T: Into<SharedCfg>>(mut self, cfg: T) -> Self {
51        self.client_config = cfg.into();
52        self
53    }
54
55    /// Start test server
56    pub fn start(self) -> TestServer {
57        let factory = self.factory;
58        let config = self.config;
59
60        let (tx, rx) = oneshot::channel();
61        // run server in separate thread
62        thread::spawn(move || {
63            let sys = System::new("ntex-test-server");
64            let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
65            let local_addr = tcp.local_addr().unwrap();
66            let system = sys.system();
67
68            sys.run(move || {
69                let server = ServerBuilder::new()
70                    .listen("test", tcp, async move |_| factory().await)?
71                    .config("test", config)
72                    .workers(1)
73                    .disable_signals()
74                    .run();
75
76                ntex_rt::spawn(async move {
77                    ntex_util::time::sleep(ntex_util::time::Millis(75)).await;
78                    tx.send((system, local_addr, server))
79                        .expect("Failed to send Server to TestServer");
80                });
81
82                Ok(())
83            })
84        });
85
86        let (system, addr, server) = rx.recv().unwrap();
87
88        TestServer {
89            addr,
90            server,
91            system,
92            cfg: self.client_config,
93        }
94    }
95}
96
97/// Start test server
98///
99/// `TestServer` is very simple test server that simplify process of writing
100/// integration tests cases for ntex web applications.
101///
102/// # Examples
103///
104/// ```rust
105/// use ntex::{server, http, client::Client};
106/// use ntex::web::{self, App, HttpResponse};
107///
108/// async fn my_handler() -> Result<HttpResponse, std::io::Error> {
109///     Ok(HttpResponse::Ok().into())
110/// }
111///
112/// #[ntex::test]
113/// async fn test_example() {
114///     let mut srv = server::test_server(
115///         async || http::HttpService::new(
116///             App::new().service(
117///                 web::resource("/").to(my_handler))
118///         )
119///     );
120///
121///     let req = Client::new().get("http://127.0.0.1:{}", srv.addr().port());
122///     let response = req.send().await.unwrap();
123///     assert!(response.status().is_success());
124/// }
125/// ```
126pub fn test_server<F, R>(factory: F) -> TestServer
127where
128    F: AsyncFn() -> R + Send + Clone + 'static,
129    R: ServiceFactory<Io, SharedCfg> + 'static,
130{
131    TestServerBuilder::new(factory).start()
132}
133
134/// Start new server with server builder
135pub fn build_test_server<F>(factory: F) -> TestServer
136where
137    F: AsyncFnOnce(ServerBuilder) -> ServerBuilder + Send + 'static,
138{
139    let (tx, rx) = oneshot::channel();
140    // run server in separate thread
141    thread::spawn(move || {
142        let sys = System::new("ntex-test-server");
143        let system = sys.system();
144
145        sys.block_on(async move {
146            let server = factory(super::build())
147                .await
148                .workers(1)
149                .disable_signals()
150                .run();
151            tx.send((system, server.clone()))
152                .expect("Failed to send Server to TestServer");
153            let _ = server.await;
154        });
155    });
156    let (system, server) = rx.recv().unwrap();
157
158    TestServer {
159        system,
160        server,
161        addr: "127.0.0.1:0".parse().unwrap(),
162        cfg: SharedCfg::new("TEST-CLIENT").into(),
163    }
164}
165
166#[derive(Debug)]
167/// Test server controller
168pub struct TestServer {
169    addr: net::SocketAddr,
170    system: System,
171    server: Server,
172    cfg: SharedCfg,
173}
174
175impl TestServer {
176    /// Test server socket addr
177    pub fn addr(&self) -> net::SocketAddr {
178        self.addr
179    }
180
181    pub fn set_addr(mut self, addr: net::SocketAddr) -> Self {
182        self.addr = addr;
183        self
184    }
185
186    /// Connect to server, return Io
187    pub async fn connect(&self) -> io::Result<Io> {
188        tcp_connect(self.addr, self.cfg).await
189    }
190
191    /// Stop http server by stopping the runtime.
192    pub fn stop(&self) {
193        self.system.stop();
194    }
195
196    /// Get first available unused address
197    pub fn unused_addr() -> net::SocketAddr {
198        let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
199        let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
200        socket.set_reuse_address(true).unwrap();
201        socket.bind(&SockAddr::from(addr)).unwrap();
202        let tcp = net::TcpListener::from(socket);
203        tcp.local_addr().unwrap()
204    }
205
206    /// Get access to the running Server
207    pub fn server(&self) -> Server {
208        self.server.clone()
209    }
210}
211
212impl Drop for TestServer {
213    fn drop(&mut self) {
214        self.stop()
215    }
216}