mirror of
https://github.com/fafhrd91/actix-web
synced 2025-06-26 06:57:43 +02:00
test-server => actix-http-test (#1807)
This commit is contained in:
80
actix-http-test/CHANGES.md
Normal file
80
actix-http-test/CHANGES.md
Normal file
@ -0,0 +1,80 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
|
||||
## 2.1.0 - 2020-11-25
|
||||
* Add ability to set address for `TestServer`. [#1645]
|
||||
* Upgrade `base64` to `0.13`.
|
||||
* Upgrade `serde_urlencoded` to `0.7`. [#1773]
|
||||
|
||||
[#1773]: https://github.com/actix/actix-web/pull/1773
|
||||
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||
|
||||
|
||||
## 2.0.0 - 2020-09-11
|
||||
* Update actix-codec and actix-utils dependencies.
|
||||
|
||||
|
||||
## 2.0.0-alpha.1 - 2020-05-23
|
||||
* Update the `time` dependency to 0.2.7
|
||||
* Update `actix-connect` dependency to 2.0.0-alpha.2
|
||||
* Make `test_server` `async` fn.
|
||||
* Bump minimum supported Rust version to 1.40
|
||||
* Replace deprecated `net2` crate with `socket2`
|
||||
* Update `base64` dependency to 0.12
|
||||
* Update `env_logger` dependency to 0.7
|
||||
|
||||
## 1.0.0 - 2019-12-13
|
||||
* Replaced `TestServer::start()` with `test_server()`
|
||||
|
||||
|
||||
## 1.0.0-alpha.3 - 2019-12-07
|
||||
* Migrate to `std::future`
|
||||
|
||||
|
||||
## 0.2.5 - 2019-09-17
|
||||
* Update serde_urlencoded to "0.6.1"
|
||||
* Increase TestServerRuntime timeouts from 500ms to 3000ms
|
||||
* Do not override current `System`
|
||||
|
||||
|
||||
## 0.2.4 - 2019-07-18
|
||||
* Update actix-server to 0.6
|
||||
|
||||
|
||||
## 0.2.3 - 2019-07-16
|
||||
* Add `delete`, `options`, `patch` methods to `TestServerRunner`
|
||||
|
||||
|
||||
## 0.2.2 - 2019-06-16
|
||||
* Add .put() and .sput() methods
|
||||
|
||||
|
||||
## 0.2.1 - 2019-06-05
|
||||
* Add license files
|
||||
|
||||
|
||||
## 0.2.0 - 2019-05-12
|
||||
* Update awc and actix-http deps
|
||||
|
||||
|
||||
## 0.1.1 - 2019-04-24
|
||||
* Always make new connection for http client
|
||||
|
||||
|
||||
## 0.1.0 - 2019-04-16
|
||||
* No changes
|
||||
|
||||
|
||||
## 0.1.0-alpha.3 - 2019-04-02
|
||||
* Request functions accept path #743
|
||||
|
||||
|
||||
## 0.1.0-alpha.2 - 2019-03-29
|
||||
* Added TestServerRuntime::load_body() method
|
||||
* Update actix-http and awc libraries
|
||||
|
||||
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
* Initial impl
|
56
actix-http-test/Cargo.toml
Normal file
56
actix-http-test/Cargo.toml
Normal file
@ -0,0 +1,56 @@
|
||||
[package]
|
||||
name = "actix-http-test"
|
||||
version = "2.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Various helpers for Actix applications to use during testing"
|
||||
readme = "README.md"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-http-test/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
exclude = [".gitignore", ".cargo/config"]
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = []
|
||||
|
||||
[lib]
|
||||
name = "actix_http_test"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# openssl
|
||||
openssl = ["open-ssl", "awc/openssl"]
|
||||
|
||||
[dependencies]
|
||||
actix-service = "1.0.6"
|
||||
actix-codec = "0.3.0"
|
||||
actix-connect = "2.0.0"
|
||||
actix-utils = "2.0.0"
|
||||
actix-rt = "1.1.1"
|
||||
actix-server = "1.0.0"
|
||||
actix-testing = "1.0.0"
|
||||
awc = "2.0.0"
|
||||
|
||||
base64 = "0.13"
|
||||
bytes = "0.5.3"
|
||||
futures-core = { version = "0.3.5", default-features = false }
|
||||
http = "0.2.0"
|
||||
log = "0.4"
|
||||
socket2 = "0.3"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
slab = "0.4"
|
||||
serde_urlencoded = "0.7"
|
||||
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
||||
open-ssl = { version = "0.10", package = "openssl", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = "3.0.0"
|
||||
actix-http = "2.0.0"
|
1
actix-http-test/LICENSE-APACHE
Symbolic link
1
actix-http-test/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
||||
../LICENSE-APACHE
|
1
actix-http-test/LICENSE-MIT
Symbolic link
1
actix-http-test/LICENSE-MIT
Symbolic link
@ -0,0 +1 @@
|
||||
../LICENSE-MIT
|
15
actix-http-test/README.md
Normal file
15
actix-http-test/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# actix-http-test
|
||||
|
||||
> Various helpers for Actix applications to use during testing.
|
||||
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://docs.rs/actix-http-test/2.1.0)
|
||||

|
||||
[](https://deps.rs/crate/actix-http-test/2.1.0)
|
||||
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-http-test)
|
||||
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||
- Minimum Supported Rust Version (MSRV): 1.42.0
|
273
actix-http-test/src/lib.rs
Normal file
273
actix-http-test/src/lib.rs
Normal file
@ -0,0 +1,273 @@
|
||||
//! Various helpers for Actix applications to use during testing.
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::{net, thread, time};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_rt::{net::TcpStream, System};
|
||||
use actix_server::{Server, ServiceFactory};
|
||||
use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector};
|
||||
use bytes::Bytes;
|
||||
use futures_core::stream::Stream;
|
||||
use http::Method;
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
pub use actix_testing::*;
|
||||
|
||||
/// Start test server
|
||||
///
|
||||
/// `TestServer` is very simple test server that simplify process of writing
|
||||
/// integration tests cases for actix web applications.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_http::HttpService;
|
||||
/// use actix_http_test::TestServer;
|
||||
/// use actix_web::{web, App, HttpResponse, Error};
|
||||
///
|
||||
/// async fn my_handler() -> Result<HttpResponse, Error> {
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }
|
||||
///
|
||||
/// #[actix_rt::test]
|
||||
/// async fn test_example() {
|
||||
/// let mut srv = TestServer::start(
|
||||
/// || HttpService::new(
|
||||
/// App::new().service(
|
||||
/// web::resource("/").to(my_handler))
|
||||
/// )
|
||||
/// );
|
||||
///
|
||||
/// let req = srv.get("/");
|
||||
/// let response = req.send().await.unwrap();
|
||||
/// assert!(response.status().is_success());
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn test_server<F: ServiceFactory<TcpStream>>(factory: F) -> TestServer {
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
test_server_with_addr(tcp, factory).await
|
||||
}
|
||||
|
||||
/// Start [`test server`](./fn.test_server.html) on a concrete Address
|
||||
pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
||||
tcp: net::TcpListener,
|
||||
factory: F,
|
||||
) -> TestServer {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// run server in separate thread
|
||||
thread::spawn(move || {
|
||||
let sys = System::new("actix-test-server");
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
|
||||
Server::build()
|
||||
.listen("test", tcp, factory)?
|
||||
.workers(1)
|
||||
.disable_signals()
|
||||
.start();
|
||||
|
||||
tx.send((System::current(), local_addr)).unwrap();
|
||||
sys.run()
|
||||
});
|
||||
|
||||
let (system, addr) = rx.recv().unwrap();
|
||||
|
||||
let client = {
|
||||
let connector = {
|
||||
#[cfg(feature = "openssl")]
|
||||
{
|
||||
use open_ssl::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())
|
||||
.finish()
|
||||
}
|
||||
#[cfg(not(feature = "openssl"))]
|
||||
{
|
||||
Connector::new()
|
||||
.conn_lifetime(time::Duration::from_secs(0))
|
||||
.timeout(time::Duration::from_millis(30000))
|
||||
.finish()
|
||||
}
|
||||
};
|
||||
|
||||
Client::builder().connector(connector).finish()
|
||||
};
|
||||
actix_connect::start_default_resolver().await.unwrap();
|
||||
|
||||
TestServer {
|
||||
addr,
|
||||
client,
|
||||
system,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 = socket.into_tcp_listener();
|
||||
tcp.local_addr().unwrap()
|
||||
}
|
||||
|
||||
/// Test server controller
|
||||
pub struct TestServer {
|
||||
addr: net::SocketAddr,
|
||||
client: Client,
|
||||
system: System,
|
||||
}
|
||||
|
||||
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 {
|
||||
if uri.starts_with('/') {
|
||||
format!("http://localhost:{}{}", self.addr.port(), uri)
|
||||
} else {
|
||||
format!("http://localhost:{}/{}", self.addr.port(), uri)
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct test https server url
|
||||
pub fn surl(&self, uri: &str) -> String {
|
||||
if uri.starts_with('/') {
|
||||
format!("https://localhost:{}{}", self.addr.port(), uri)
|
||||
} else {
|
||||
format!("https://localhost:{}/{}", 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 https `GET` request
|
||||
pub fn sget<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.get(self.surl(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 https `POST` request
|
||||
pub fn spost<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.post(self.surl(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 https `HEAD` request
|
||||
pub fn shead<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.head(self.surl(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 https `PUT` request
|
||||
pub fn sput<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.put(self.surl(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 https `PATCH` request
|
||||
pub fn spatch<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.patch(self.surl(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 https `DELETE` request
|
||||
pub fn sdelete<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.delete(self.surl(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())
|
||||
}
|
||||
|
||||
/// Create https `OPTIONS` request
|
||||
pub fn soptions<S: AsRef<str>>(&self, path: S) -> ClientRequest {
|
||||
self.client.options(self.surl(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
|
||||
}
|
||||
|
||||
/// Stop http server
|
||||
fn stop(&mut self) {
|
||||
self.system.stop();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestServer {
|
||||
fn drop(&mut self) {
|
||||
self.stop()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user