mirror of https://github.com/fafhrd91/actix-web
migrate integration testing to new crate
This commit is contained in:
parent
222acfd070
commit
3c06993401
|
@ -11,9 +11,12 @@
|
|||
### Removed
|
||||
* The `client` mod was removed. Clients should now use `awc` directly.
|
||||
[871ca5e4](https://github.com/actix/actix-web/commit/871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7)
|
||||
* Integration testing was moved to new `actix-test` crate. Namely these items from the `test`
|
||||
module: `TestServer`, `TestServerConfig`, `start`, `start_with`, and `unused_addr`. [#???]
|
||||
|
||||
[#2067]: https://github.com/actix/actix-web/pull/2067
|
||||
[#2093]: https://github.com/actix/actix-web/pull/2093
|
||||
[#???]: https://github.com/actix/actix-web/pull/???
|
||||
|
||||
|
||||
## 4.0.0-beta.4 - 2021-03-09
|
||||
|
|
15
Cargo.toml
15
Cargo.toml
|
@ -36,25 +36,26 @@ members = [
|
|||
"actix-web-actors",
|
||||
"actix-web-codegen",
|
||||
"actix-http-test",
|
||||
"actix-test",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["compress", "cookies"]
|
||||
|
||||
# content-encoding support
|
||||
compress = ["actix-http/compress", "awc/compress"]
|
||||
compress = ["actix-http/compress"]
|
||||
|
||||
# support for cookies
|
||||
cookies = ["actix-http/cookies", "awc/cookies"]
|
||||
cookies = ["actix-http/cookies"]
|
||||
|
||||
# secure cookies feature
|
||||
secure-cookies = ["actix-http/secure-cookies"]
|
||||
|
||||
# openssl
|
||||
openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"]
|
||||
openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl"]
|
||||
|
||||
# rustls
|
||||
rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"]
|
||||
rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls"]
|
||||
|
||||
[[example]]
|
||||
name = "basic"
|
||||
|
@ -72,10 +73,6 @@ required-features = ["compress", "cookies"]
|
|||
name = "on_connect"
|
||||
required-features = []
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["rustls"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.0-beta.1"
|
||||
actix-macros = "0.2.0"
|
||||
|
@ -88,7 +85,7 @@ actix-tls = { version = "3.0.0-beta.5", default-features = false, optional = tru
|
|||
|
||||
actix-web-codegen = "0.5.0-beta.2"
|
||||
actix-http = "3.0.0-beta.4"
|
||||
awc = { version = "3.0.0-beta.3", default-features = false }
|
||||
# awc = { version = "3.0.0-beta.3", default-features = false }
|
||||
|
||||
ahash = "0.7"
|
||||
bytes = "1"
|
||||
|
|
|
@ -115,16 +115,6 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Get first available unused address
|
||||
pub fn unused_addr() -> net::SocketAddr {
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap();
|
||||
socket.bind(&addr.into()).unwrap();
|
||||
socket.set_reuse_address(true).unwrap();
|
||||
let tcp = net::TcpListener::from(socket);
|
||||
tcp.local_addr().unwrap()
|
||||
}
|
||||
|
||||
/// Test server controller
|
||||
pub struct TestServer {
|
||||
addr: net::SocketAddr,
|
||||
|
@ -269,3 +259,13 @@ impl Drop for TestServer {
|
|||
self.stop()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get first available unused address
|
||||
pub fn unused_addr() -> net::SocketAddr {
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap();
|
||||
socket.bind(&addr.into()).unwrap();
|
||||
socket.set_reuse_address(true).unwrap();
|
||||
let tcp = net::TcpListener::from(socket);
|
||||
tcp.local_addr().unwrap()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Move integration testing structs from `actix-web`. [#???]
|
||||
|
||||
[#???]: https://github.com/actix/actix-web/pull/???
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "actix-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Rob Ede <robjtede@icloud.com>"]
|
||||
edition = "2018"
|
||||
description = "Integration testing tools for Actix Web applications"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.0-beta.1"
|
||||
actix-http = { version = "3.0.0-beta.4", features = ["cookies"] }
|
||||
actix-http-test = { version = "3.0.0-beta.3", features = [] }
|
||||
actix-service = "2.0.0-beta.4"
|
||||
actix-utils = "3.0.0-beta.2"
|
||||
actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3.0.0-beta.3", default-features = false, features = ["cookies"] }
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_urlencoded = "0.7"
|
|
@ -0,0 +1 @@
|
|||
../LICENSE-APACHE
|
|
@ -0,0 +1 @@
|
|||
../LICENSE-MIT
|
|
@ -0,0 +1,455 @@
|
|||
//! Integration testing tools for Actix Web applications.
|
||||
//!
|
||||
//! The main integration testing tool is [`TestServer`]. It spawns a real HTTP server on an
|
||||
//! unused port and provides methods that use a real HTTP client. Therefore, it is much closer to
|
||||
//! real-world cases than using `init_service`, which skips HTTP encoding and decoding.
|
||||
//!
|
||||
//! # Examples
|
||||
//! ```
|
||||
//! use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
|
||||
//!
|
||||
//! #[get("/")]
|
||||
//! async fn my_handler() -> Result<impl Responder, Error> {
|
||||
//! Ok(HttpResponse::Ok())
|
||||
//! }
|
||||
//!
|
||||
//! #[actix_rt::test]
|
||||
//! async fn test_example() {
|
||||
//! let srv = test::start(||
|
||||
//! App::new().service(my_handler)
|
||||
//! );
|
||||
//!
|
||||
//! let req = srv.get("/");
|
||||
//! let res = req.send().await.unwrap();
|
||||
//!
|
||||
//! assert!(res.status().is_success());
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::{fmt, net, sync::mpsc, thread, time};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
pub use actix_http::test::TestBuffer;
|
||||
use actix_http::{http::Method, ws, HttpService, Request};
|
||||
use actix_service::{map_config, IntoServiceFactory, ServiceFactory};
|
||||
use actix_web::{
|
||||
dev::{AppConfig, MessageBody, Server, Service},
|
||||
rt,
|
||||
web::Bytes,
|
||||
Error, HttpResponse,
|
||||
};
|
||||
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
||||
use futures_core::Stream;
|
||||
|
||||
pub use actix_http_test::unused_addr;
|
||||
pub use actix_web::test::{
|
||||
call_service, default_service, init_service, load_stream, ok_service, read_body,
|
||||
read_body_json, read_response, read_response_json, TestRequest,
|
||||
};
|
||||
|
||||
/// Start default test server.
|
||||
///
|
||||
/// Test server is very simple server that simplify process of writing
|
||||
/// integration tests cases for actix web applications.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::{web, test, App, HttpResponse, Error};
|
||||
///
|
||||
/// async fn my_handler() -> Result<HttpResponse, Error> {
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// async fn test_example() {
|
||||
/// let srv = test::start(
|
||||
/// || App::new().service(
|
||||
/// web::resource("/").to(my_handler))
|
||||
/// );
|
||||
///
|
||||
/// let req = srv.get("/");
|
||||
/// let response = req.send().await.unwrap();
|
||||
/// assert!(response.status().is_success());
|
||||
/// }
|
||||
/// ```
|
||||
pub fn start<F, I, S, B>(factory: F) -> TestServer
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoServiceFactory<S, Request>,
|
||||
S: ServiceFactory<Request, Config = AppConfig> + 'static,
|
||||
S::Error: Into<Error> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<HttpResponse<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
start_with(TestServerConfig::default(), factory)
|
||||
}
|
||||
|
||||
/// Start test server with custom configuration
|
||||
///
|
||||
/// Check [`TestServerConfig`] docs for configuration options.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::{web, test, App, HttpResponse, Error, Responder};
|
||||
///
|
||||
/// async fn my_handler() -> Result<impl Responder, Error> {
|
||||
/// Ok(HttpResponse::Ok())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// async fn test_example() {
|
||||
/// let srv = test::start_with(test::config().h1(), ||
|
||||
/// App::new().service(web::resource("/").to(my_handler))
|
||||
/// );
|
||||
///
|
||||
/// let req = srv.get("/");
|
||||
/// let response = req.send().await.unwrap();
|
||||
/// assert!(response.status().is_success());
|
||||
/// }
|
||||
/// ```
|
||||
pub fn start_with<F, I, S, B>(cfg: TestServerConfig, factory: F) -> TestServer
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoServiceFactory<S, Request>,
|
||||
S: ServiceFactory<Request, Config = AppConfig> + 'static,
|
||||
S::Error: Into<Error> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<HttpResponse<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let tls = match cfg.stream {
|
||||
StreamType::Tcp => false,
|
||||
#[cfg(feature = "openssl")]
|
||||
StreamType::Openssl(_) => true,
|
||||
#[cfg(feature = "rustls")]
|
||||
StreamType::Rustls(_) => true,
|
||||
};
|
||||
|
||||
// run server in separate thread
|
||||
thread::spawn(move || {
|
||||
let sys = rt::System::new();
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
let factory = factory.clone();
|
||||
let srv_cfg = cfg.clone();
|
||||
let timeout = cfg.client_timeout;
|
||||
let builder = Server::build().workers(1).disable_signals();
|
||||
|
||||
let srv = match srv_cfg.stream {
|
||||
StreamType::Tcp => match srv_cfg.tp {
|
||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.h1(map_config(factory(), move |_| app_cfg.clone()))
|
||||
.tcp()
|
||||
}),
|
||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.h2(map_config(factory(), move |_| app_cfg.clone()))
|
||||
.tcp()
|
||||
}),
|
||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.finish(map_config(factory(), move |_| app_cfg.clone()))
|
||||
.tcp()
|
||||
}),
|
||||
},
|
||||
#[cfg(feature = "openssl")]
|
||||
StreamType::Openssl(acceptor) => match cfg.tp {
|
||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.h1(map_config(factory(), |_| cfg.clone()))
|
||||
.openssl(acceptor.clone())
|
||||
}),
|
||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.h2(map_config(factory(), |_| cfg.clone()))
|
||||
.openssl(acceptor.clone())
|
||||
}),
|
||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.finish(map_config(factory(), |_| cfg.clone()))
|
||||
.openssl(acceptor.clone())
|
||||
}),
|
||||
},
|
||||
#[cfg(feature = "rustls")]
|
||||
StreamType::Rustls(config) => match cfg.tp {
|
||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.h1(map_config(factory(), |_| cfg.clone()))
|
||||
.rustls(config.clone())
|
||||
}),
|
||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.h2(map_config(factory(), |_| cfg.clone()))
|
||||
.rustls(config.clone())
|
||||
}),
|
||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||
let app_cfg =
|
||||
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
.finish(map_config(factory(), |_| cfg.clone()))
|
||||
.rustls(config.clone())
|
||||
}),
|
||||
},
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
sys.block_on(async {
|
||||
let srv = srv.run();
|
||||
tx.send((rt::System::current(), srv, local_addr)).unwrap();
|
||||
});
|
||||
|
||||
sys.run()
|
||||
});
|
||||
|
||||
let (system, server, addr) = rx.recv().unwrap();
|
||||
|
||||
let client = {
|
||||
let connector = {
|
||||
#[cfg(feature = "openssl")]
|
||||
{
|
||||
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
||||
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
let _ = builder
|
||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
.ssl(builder.build())
|
||||
}
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
{
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
}
|
||||
};
|
||||
|
||||
Client::builder().connector(connector).finish()
|
||||
};
|
||||
|
||||
TestServer {
|
||||
addr,
|
||||
client,
|
||||
system,
|
||||
tls,
|
||||
server,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum HttpVer {
|
||||
Http1,
|
||||
Http2,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum StreamType {
|
||||
Tcp,
|
||||
#[cfg(feature = "openssl")]
|
||||
Openssl(openssl::ssl::SslAcceptor),
|
||||
#[cfg(feature = "rustls")]
|
||||
Rustls(rustls::ServerConfig),
|
||||
}
|
||||
|
||||
/// Create default test server config.
|
||||
pub fn config() -> TestServerConfig {
|
||||
TestServerConfig::default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestServerConfig {
|
||||
tp: HttpVer,
|
||||
stream: StreamType,
|
||||
client_timeout: u64,
|
||||
}
|
||||
|
||||
impl Default for TestServerConfig {
|
||||
fn default() -> Self {
|
||||
TestServerConfig::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestServerConfig {
|
||||
/// Create default server configuration
|
||||
pub(crate) fn new() -> TestServerConfig {
|
||||
TestServerConfig {
|
||||
tp: HttpVer::Both,
|
||||
stream: StreamType::Tcp,
|
||||
client_timeout: 5000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept HTTP/1.1 only.
|
||||
pub fn h1(mut self) -> Self {
|
||||
self.tp = HttpVer::Http1;
|
||||
self
|
||||
}
|
||||
|
||||
/// Accept HTTP/2 only.
|
||||
pub fn h2(mut self) -> Self {
|
||||
self.tp = HttpVer::Http2;
|
||||
self
|
||||
}
|
||||
|
||||
/// Accept secure connections via OpenSSL.
|
||||
#[cfg(feature = "openssl")]
|
||||
pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self {
|
||||
self.stream = StreamType::Openssl(acceptor);
|
||||
self
|
||||
}
|
||||
|
||||
/// Accept secure connections via Rustls.
|
||||
#[cfg(feature = "rustls")]
|
||||
pub fn rustls(mut self, config: rustls::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls(config);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set client timeout in milliseconds for first request.
|
||||
pub fn client_timeout(mut self, val: u64) -> Self {
|
||||
self.client_timeout = val;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Test server controller.
|
||||
pub struct TestServer {
|
||||
addr: net::SocketAddr,
|
||||
client: awc::Client,
|
||||
system: rt::System,
|
||||
tls: bool,
|
||||
server: Server,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
/// Construct test server url
|
||||
pub fn addr(&self) -> net::SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
/// Construct test server url
|
||||
pub fn url(&self, uri: &str) -> String {
|
||||
let scheme = if self.tls { "https" } else { "http" };
|
||||
|
||||
if uri.starts_with('/') {
|
||||
format!("{}://localhost:{}{}", scheme, self.addr.port(), uri)
|
||||
} else {
|
||||
format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create `GET` request.
|
||||
pub fn get(&self, path: impl AsRef<str>) -> ClientRequest {
|
||||
self.client.get(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `POST` request.
|
||||
pub fn post(&self, path: impl AsRef<str>) -> ClientRequest {
|
||||
self.client.post(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `HEAD` request.
|
||||
pub fn head(&self, path: impl AsRef<str>) -> ClientRequest {
|
||||
self.client.head(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `PUT` request.
|
||||
pub fn put(&self, path: impl AsRef<str>) -> ClientRequest {
|
||||
self.client.put(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `PATCH` request.
|
||||
pub fn patch(&self, path: impl AsRef<str>) -> ClientRequest {
|
||||
self.client.patch(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `DELETE` request.
|
||||
pub fn delete(&self, path: impl AsRef<str>) -> ClientRequest {
|
||||
self.client.delete(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `OPTIONS` request.
|
||||
pub fn options(&self, path: impl AsRef<str>) -> ClientRequest {
|
||||
self.client.options(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Connect request with given method and path.
|
||||
pub fn request(&self, method: Method, path: impl AsRef<str>) -> ClientRequest {
|
||||
self.client.request(method, path.as_ref())
|
||||
}
|
||||
|
||||
pub async fn load_body<S>(
|
||||
&mut self,
|
||||
mut response: ClientResponse<S>,
|
||||
) -> Result<Bytes, PayloadError>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin + 'static,
|
||||
{
|
||||
response.body().limit(10_485_760).await
|
||||
}
|
||||
|
||||
/// Connect to WebSocket server at a given path.
|
||||
pub async fn ws_at(
|
||||
&mut self,
|
||||
path: &str,
|
||||
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
|
||||
let url = self.url(path);
|
||||
let connect = self.client.ws(url).connect();
|
||||
connect.await.map(|(_, framed)| framed)
|
||||
}
|
||||
|
||||
/// Connect to a WebSocket server.
|
||||
pub async fn ws(
|
||||
&mut self,
|
||||
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
|
||||
self.ws_at("/").await
|
||||
}
|
||||
|
||||
/// Gracefully stop HTTP server.
|
||||
pub async fn stop(self) {
|
||||
self.server.stop(true).await;
|
||||
self.system.stop();
|
||||
rt::time::sleep(time::Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServer {
|
||||
fn drop(&mut self) {
|
||||
self.system.stop()
|
||||
}
|
||||
}
|
|
@ -79,3 +79,7 @@ flate2 = "1.0.13"
|
|||
futures-util = { version = "0.3.7", default-features = false }
|
||||
rcgen = "0.8"
|
||||
webpki = "0.21"
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["rustls"]
|
||||
|
|
|
@ -11,6 +11,6 @@ coverage:
|
|||
|
||||
ignore: # ignore code coverage on following paths
|
||||
- "**/tests"
|
||||
- "test-server"
|
||||
- "**/benches"
|
||||
- "**/examples"
|
||||
- "test-server"
|
||||
|
|
|
@ -112,10 +112,15 @@ pub struct AppConfig {
|
|||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self {
|
||||
pub(crate) fn new(secure: bool, host: String, addr: SocketAddr) -> Self {
|
||||
AppConfig { secure, host, addr }
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self {
|
||||
AppConfig::new(secure, host, addr)
|
||||
}
|
||||
|
||||
/// Server host name.
|
||||
///
|
||||
/// Host name is used by application router as a hostname for url generation.
|
||||
|
@ -142,8 +147,8 @@ impl Default for AppConfig {
|
|||
fn default() -> Self {
|
||||
AppConfig::new(
|
||||
false,
|
||||
"127.0.0.1:8080".parse().unwrap(),
|
||||
"localhost:8080".to_owned(),
|
||||
"127.0.0.1:8080".parse().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,18 @@ impl HttpRequest {
|
|||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn __priv_test_new(
|
||||
path: Path<Url>,
|
||||
head: Message<RequestHead>,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
app_data: Rc<Extensions>,
|
||||
) -> HttpRequest {
|
||||
let app_state = AppInitServiceState::new(rmap, config);
|
||||
Self::new(path, head, app_state, app_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpRequest {
|
||||
|
|
|
@ -288,7 +288,7 @@ where
|
|||
};
|
||||
|
||||
svc.finish(map_config(factory(), move |_| {
|
||||
AppConfig::new(false, addr, host.clone())
|
||||
AppConfig::new(false, host.clone(), addr)
|
||||
}))
|
||||
.tcp()
|
||||
})?;
|
||||
|
@ -502,8 +502,8 @@ where
|
|||
let c = cfg.lock().unwrap();
|
||||
let config = AppConfig::new(
|
||||
false,
|
||||
socket_addr,
|
||||
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
||||
socket_addr,
|
||||
);
|
||||
|
||||
pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) })
|
||||
|
@ -552,8 +552,8 @@ where
|
|||
let c = cfg.lock().unwrap();
|
||||
let config = AppConfig::new(
|
||||
false,
|
||||
socket_addr,
|
||||
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
||||
socket_addr,
|
||||
);
|
||||
pipeline_factory(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) })
|
||||
.and_then(
|
||||
|
|
|
@ -69,6 +69,12 @@ impl ServiceRequest {
|
|||
Self { req, payload }
|
||||
}
|
||||
|
||||
/// Construct service request.
|
||||
#[doc(hidden)]
|
||||
pub fn __priv_test_new(req: HttpRequest, payload: Payload) -> Self {
|
||||
Self::new(req, payload)
|
||||
}
|
||||
|
||||
/// Deconstruct request into parts
|
||||
#[inline]
|
||||
pub fn into_parts(self) -> (HttpRequest, Payload) {
|
||||
|
|
470
src/test.rs
470
src/test.rs
|
@ -1,39 +1,34 @@
|
|||
//! Various helpers for Actix applications to use during testing.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc;
|
||||
use std::{fmt, net, thread, time};
|
||||
use std::{net::SocketAddr, rc::Rc};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
#[cfg(feature = "cookies")]
|
||||
use actix_http::cookie::Cookie;
|
||||
use actix_http::http::header::{ContentType, IntoHeaderPair};
|
||||
use actix_http::http::{Method, StatusCode, Uri, Version};
|
||||
use actix_http::test::TestRequest as HttpTestRequest;
|
||||
use actix_http::{ws, Extensions, HttpService, Request};
|
||||
use actix_router::{Path, ResourceDef, Url};
|
||||
use actix_rt::{time::sleep, System};
|
||||
use actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory};
|
||||
use awc::error::PayloadError;
|
||||
use awc::{Client, ClientRequest, ClientResponse, Connector};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::Stream;
|
||||
use futures_util::future::ok;
|
||||
use futures_util::StreamExt;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
pub use actix_http::test::TestBuffer;
|
||||
use actix_http::{
|
||||
http::{
|
||||
header::{ContentType, IntoHeaderPair},
|
||||
Method, StatusCode, Uri, Version,
|
||||
},
|
||||
test::TestRequest as HttpTestRequest,
|
||||
Extensions, Request,
|
||||
};
|
||||
use actix_router::{Path, ResourceDef, Url};
|
||||
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
|
||||
use futures_core::Stream;
|
||||
use futures_util::{future::ok, StreamExt};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use crate::app_service::AppInitServiceState;
|
||||
use crate::config::AppConfig;
|
||||
use crate::data::Data;
|
||||
use crate::dev::{Body, MessageBody, Payload, Server};
|
||||
use crate::rmap::ResourceMap;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::{Error, HttpRequest, HttpResponse};
|
||||
use crate::{
|
||||
app_service::AppInitServiceState,
|
||||
config::AppConfig,
|
||||
data::Data,
|
||||
dev::{Body, MessageBody, Payload},
|
||||
rmap::ResourceMap,
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
web::{Bytes, BytesMut},
|
||||
Error, HttpRequest, HttpResponse,
|
||||
};
|
||||
|
||||
/// Create service that always responds with `HttpResponse::Ok()`
|
||||
pub fn ok_service(
|
||||
|
@ -83,7 +78,7 @@ where
|
|||
{
|
||||
try_init_service(app)
|
||||
.await
|
||||
.expect("service initilization failed")
|
||||
.expect("service initialization failed")
|
||||
}
|
||||
|
||||
/// Fallible version of init_service that allows testing data factory errors.
|
||||
|
@ -162,13 +157,15 @@ where
|
|||
let mut resp = app
|
||||
.call(req)
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("read_response failed at application call"));
|
||||
.expect("read_response failed at application call");
|
||||
|
||||
let mut body = resp.take_body();
|
||||
let mut bytes = BytesMut::new();
|
||||
|
||||
while let Some(item) = body.next().await {
|
||||
bytes.extend_from_slice(&item.unwrap());
|
||||
}
|
||||
|
||||
bytes.freeze()
|
||||
}
|
||||
|
||||
|
@ -565,417 +562,6 @@ impl TestRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// Start test server with default configuration
|
||||
///
|
||||
/// Test server is very simple server that simplify process of writing
|
||||
/// integration tests cases for actix web applications.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::{web, test, App, HttpResponse, Error};
|
||||
///
|
||||
/// async fn my_handler() -> Result<HttpResponse, Error> {
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// async fn test_example() {
|
||||
/// let srv = test::start(
|
||||
/// || App::new().service(
|
||||
/// web::resource("/").to(my_handler))
|
||||
/// );
|
||||
///
|
||||
/// let req = srv.get("/");
|
||||
/// let response = req.send().await.unwrap();
|
||||
/// assert!(response.status().is_success());
|
||||
/// }
|
||||
/// ```
|
||||
pub fn start<F, I, S, B>(factory: F) -> TestServer
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoServiceFactory<S, Request>,
|
||||
S: ServiceFactory<Request, Config = AppConfig> + 'static,
|
||||
S::Error: Into<Error> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<HttpResponse<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
start_with(TestServerConfig::default(), factory)
|
||||
}
|
||||
|
||||
/// Start test server with custom configuration
|
||||
///
|
||||
/// Test server could be configured in different ways, for details check
|
||||
/// `TestServerConfig` docs.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::{web, test, App, HttpResponse, Error};
|
||||
///
|
||||
/// async fn my_handler() -> Result<HttpResponse, Error> {
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// async fn test_example() {
|
||||
/// let srv = test::start_with(test::config().h1(), ||
|
||||
/// App::new().service(web::resource("/").to(my_handler))
|
||||
/// );
|
||||
///
|
||||
/// let req = srv.get("/");
|
||||
/// let response = req.send().await.unwrap();
|
||||
/// assert!(response.status().is_success());
|
||||
/// }
|
||||
/// ```
|
||||
pub fn start_with<F, I, S, B>(cfg: TestServerConfig, factory: F) -> TestServer
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoServiceFactory<S, Request>,
|
||||
S: ServiceFactory<Request, Config = AppConfig> + 'static,
|
||||
S::Error: Into<Error> + 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<HttpResponse<B>> + 'static,
|
||||
<S::Service as Service<Request>>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let ssl = match cfg.stream {
|
||||
StreamType::Tcp => false,
|
||||
#[cfg(feature = "openssl")]
|
||||
StreamType::Openssl(_) => true,
|
||||
#[cfg(feature = "rustls")]
|
||||
StreamType::Rustls(_) => true,
|
||||
};
|
||||
|
||||
// run server in separate thread
|
||||
thread::spawn(move || {
|
||||
let sys = System::new();
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
let factory = factory.clone();
|
||||
let cfg = cfg.clone();
|
||||
let ctimeout = cfg.client_timeout;
|
||||
let builder = Server::build().workers(1).disable_signals();
|
||||
|
||||
let srv = match cfg.stream {
|
||||
StreamType::Tcp => match cfg.tp {
|
||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.h1(map_config(factory(), move |_| cfg.clone()))
|
||||
.tcp()
|
||||
}),
|
||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.h2(map_config(factory(), move |_| cfg.clone()))
|
||||
.tcp()
|
||||
}),
|
||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
||||
.tcp()
|
||||
}),
|
||||
},
|
||||
#[cfg(feature = "openssl")]
|
||||
StreamType::Openssl(acceptor) => match cfg.tp {
|
||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.h1(map_config(factory(), move |_| cfg.clone()))
|
||||
.openssl(acceptor.clone())
|
||||
}),
|
||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.h2(map_config(factory(), move |_| cfg.clone()))
|
||||
.openssl(acceptor.clone())
|
||||
}),
|
||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
||||
.openssl(acceptor.clone())
|
||||
}),
|
||||
},
|
||||
#[cfg(feature = "rustls")]
|
||||
StreamType::Rustls(config) => match cfg.tp {
|
||||
HttpVer::Http1 => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.h1(map_config(factory(), move |_| cfg.clone()))
|
||||
.rustls(config.clone())
|
||||
}),
|
||||
HttpVer::Http2 => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.h2(map_config(factory(), move |_| cfg.clone()))
|
||||
.rustls(config.clone())
|
||||
}),
|
||||
HttpVer::Both => builder.listen("test", tcp, move || {
|
||||
let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr));
|
||||
HttpService::build()
|
||||
.client_timeout(ctimeout)
|
||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
||||
.rustls(config.clone())
|
||||
}),
|
||||
},
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
sys.block_on(async {
|
||||
let srv = srv.run();
|
||||
tx.send((System::current(), srv, local_addr)).unwrap();
|
||||
});
|
||||
|
||||
sys.run()
|
||||
});
|
||||
|
||||
let (system, server, addr) = rx.recv().unwrap();
|
||||
|
||||
let client = {
|
||||
let connector = {
|
||||
#[cfg(feature = "openssl")]
|
||||
{
|
||||
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
||||
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
let _ = builder
|
||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
.ssl(builder.build())
|
||||
}
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
{
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
}
|
||||
};
|
||||
|
||||
Client::builder().connector(connector).finish()
|
||||
};
|
||||
|
||||
TestServer {
|
||||
addr,
|
||||
client,
|
||||
system,
|
||||
ssl,
|
||||
server,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestServerConfig {
|
||||
tp: HttpVer,
|
||||
stream: StreamType,
|
||||
client_timeout: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum HttpVer {
|
||||
Http1,
|
||||
Http2,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum StreamType {
|
||||
Tcp,
|
||||
#[cfg(feature = "openssl")]
|
||||
Openssl(openssl::ssl::SslAcceptor),
|
||||
#[cfg(feature = "rustls")]
|
||||
Rustls(rustls::ServerConfig),
|
||||
}
|
||||
|
||||
impl Default for TestServerConfig {
|
||||
fn default() -> Self {
|
||||
TestServerConfig::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create default test server config
|
||||
pub fn config() -> TestServerConfig {
|
||||
TestServerConfig::new()
|
||||
}
|
||||
|
||||
impl TestServerConfig {
|
||||
/// Create default server configuration
|
||||
pub(crate) fn new() -> TestServerConfig {
|
||||
TestServerConfig {
|
||||
tp: HttpVer::Both,
|
||||
stream: StreamType::Tcp,
|
||||
client_timeout: 5000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start HTTP/1.1 server only
|
||||
pub fn h1(mut self) -> Self {
|
||||
self.tp = HttpVer::Http1;
|
||||
self
|
||||
}
|
||||
|
||||
/// Start HTTP/2 server only
|
||||
pub fn h2(mut self) -> Self {
|
||||
self.tp = HttpVer::Http2;
|
||||
self
|
||||
}
|
||||
|
||||
/// Start openssl server
|
||||
#[cfg(feature = "openssl")]
|
||||
pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self {
|
||||
self.stream = StreamType::Openssl(acceptor);
|
||||
self
|
||||
}
|
||||
|
||||
/// Start rustls server
|
||||
#[cfg(feature = "rustls")]
|
||||
pub fn rustls(mut self, config: rustls::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls(config);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server client timeout in milliseconds for first request.
|
||||
pub fn client_timeout(mut self, val: u64) -> Self {
|
||||
self.client_timeout = val;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Get first available unused address
|
||||
pub fn unused_addr() -> net::SocketAddr {
|
||||
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap();
|
||||
socket.bind(&addr.into()).unwrap();
|
||||
socket.set_reuse_address(true).unwrap();
|
||||
let tcp = net::TcpListener::from(socket);
|
||||
tcp.local_addr().unwrap()
|
||||
}
|
||||
|
||||
/// Test server controller
|
||||
pub struct TestServer {
|
||||
addr: net::SocketAddr,
|
||||
client: awc::Client,
|
||||
system: actix_rt::System,
|
||||
ssl: bool,
|
||||
server: Server,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
/// Construct test server url
|
||||
pub fn addr(&self) -> net::SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
/// Construct test server url
|
||||
pub fn url(&self, uri: &str) -> String {
|
||||
let scheme = if self.ssl { "https" } else { "http" };
|
||||
|
||||
if uri.starts_with('/') {
|
||||
format!("{}://localhost:{}{}", scheme, self.addr.port(), uri)
|
||||
} else {
|
||||
format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create `GET` request
|
||||
pub fn get<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.get(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `POST` request
|
||||
pub fn post<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.post(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `HEAD` request
|
||||
pub fn head<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.head(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `PUT` request
|
||||
pub fn put<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.put(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `PATCH` request
|
||||
pub fn patch<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.patch(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `DELETE` request
|
||||
pub fn delete<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.delete(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Create `OPTIONS` request
|
||||
pub fn options<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.options(self.url(path.as_ref()).as_str())
|
||||
}
|
||||
|
||||
/// Connect to test HTTP server
|
||||
pub fn request<S: AsRef<str>>(&self, method: Method, path: S) -> ClientRequest {
|
||||
self.client.request(method, path.as_ref())
|
||||
}
|
||||
|
||||
pub async fn load_body<S>(
|
||||
&mut self,
|
||||
mut response: ClientResponse<S>,
|
||||
) -> Result<Bytes, PayloadError>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin + 'static,
|
||||
{
|
||||
response.body().limit(10_485_760).await
|
||||
}
|
||||
|
||||
/// Connect to WebSocket server at a given path.
|
||||
pub async fn ws_at(
|
||||
&mut self,
|
||||
path: &str,
|
||||
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
|
||||
let url = self.url(path);
|
||||
let connect = self.client.ws(url).connect();
|
||||
connect.await.map(|(_, framed)| framed)
|
||||
}
|
||||
|
||||
/// Connect to a WebSocket server.
|
||||
pub async fn ws(
|
||||
&mut self,
|
||||
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
|
||||
self.ws_at("/").await
|
||||
}
|
||||
|
||||
/// Gracefully stop HTTP server
|
||||
pub async fn stop(self) {
|
||||
self.server.stop(true).await;
|
||||
self.system.stop();
|
||||
sleep(time::Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServer {
|
||||
fn drop(&mut self) {
|
||||
self.system.stop()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_http::HttpMessage;
|
||||
|
|
Loading…
Reference in New Issue