1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01:00

add TestServiceRequest builder

This commit is contained in:
Nikolay Kim 2019-03-02 16:24:14 -08:00
parent 9394a4e2a5
commit e4198a037a
6 changed files with 229 additions and 259 deletions

View File

@ -44,13 +44,11 @@ flate2-rust = ["flate2/rust_backend"]
[dependencies] [dependencies]
actix-codec = "0.1.0" actix-codec = "0.1.0"
#actix-service = "0.2.1" actix-service = "0.3.0"
#actix-server = "0.2.1" actix-utils = "0.3.0"
#actix-utils = "0.2.1"
actix-utils = { git = "https://github.com/actix/actix-net.git" }
actix-http = { git = "https://github.com/actix/actix-http.git" } actix-http = { git = "https://github.com/actix/actix-http.git" }
actix-router = { git = "https://github.com/actix/actix-net.git" } actix-router = { git = "https://github.com/actix/actix-net.git" }
actix-service = { git = "https://github.com/actix/actix-net.git" }
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
@ -73,8 +71,7 @@ flate2 = { version="^1.0.2", optional = true, default-features = false }
[dev-dependencies] [dev-dependencies]
actix-rt = "0.1.0" actix-rt = "0.1.0"
#actix-server = { version="0.2", features=["ssl"] } actix-server = { version="0.3.0", features=["ssl"] }
actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] }
actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
rand = "0.6" rand = "0.6"

View File

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use actix_http::body::MessageBody; use actix_http::body::{Body, MessageBody};
use actix_http::{Extensions, PayloadStream, Request, Response}; use actix_http::{Extensions, PayloadStream, Request, Response};
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::boxed::{self, BoxedNewService, BoxedService};
@ -130,7 +130,7 @@ where
/// }); /// });
/// } /// }
/// ``` /// ```
pub fn resource<F, U, B>(self, path: &str, f: F) -> AppRouter<T, P, B, AppEntry<P>> pub fn resource<F, U>(self, path: &str, f: F) -> AppRouter<T, P, Body, AppEntry<P>>
where where
F: FnOnce(Resource<P>) -> Resource<P, U>, F: FnOnce(Resource<P>) -> Resource<P, U>,
U: NewService< U: NewService<
@ -159,16 +159,16 @@ where
} }
/// Register a middleware. /// Register a middleware.
pub fn middleware<M, B, F>( pub fn middleware<M, F>(
self, self,
mw: F, mw: F,
) -> AppRouter< ) -> AppRouter<
T, T,
P, P,
B, Body,
impl NewService< impl NewService<
Request = ServiceRequest<P>, Request = ServiceRequest<P>,
Response = ServiceResponse<B>, Response = ServiceResponse,
Error = (), Error = (),
InitError = (), InitError = (),
>, >,
@ -177,11 +177,10 @@ where
M: NewTransform< M: NewTransform<
AppService<P>, AppService<P>,
Request = ServiceRequest<P>, Request = ServiceRequest<P>,
Response = ServiceResponse<B>, Response = ServiceResponse,
Error = (), Error = (),
InitError = (), InitError = (),
>, >,
B: MessageBody,
F: IntoNewTransform<M, AppService<P>>, F: IntoNewTransform<M, AppService<P>>,
{ {
let fref = Rc::new(RefCell::new(None)); let fref = Rc::new(RefCell::new(None));

View File

@ -16,14 +16,13 @@ pub trait Filter {
/// Return filter that matches if any of supplied filters. /// Return filter that matches if any of supplied filters.
/// ///
/// ```rust,ignore /// ```rust,ignore
/// # extern crate actix_web; /// use actix_web::{filter, App, HttpResponse};
/// use actix_web2::{filter, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// App::new().resource("/index.html", |r| { /// App::new().resource("/index.html", |r| {
/// r.route() /// r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Post())) /// .filter(pred::Any(pred::Get()).or(pred::Post()))
/// .f(|r| HttpResponse::MethodNotAllowed()) /// .to(|| HttpResponse::MethodNotAllowed())
/// }); /// });
/// } /// }
/// ``` /// ```
@ -55,18 +54,18 @@ impl Filter for AnyFilter {
/// Return filter that matches if all of supplied filters match. /// Return filter that matches if all of supplied filters match.
/// ///
/// ```rust,ignore /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse}; /// use actix_web::{filter, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// App::new().resource("/index.html", |r| { /// App::new().resource("/index.html", |r| {
/// r.route() /// r.route(
/// .filter( /// |r| r.filter(
/// pred::All(pred::Get()) /// filter::All(filter::Get())
/// .and(pred::Header("content-type", "text/plain")), /// .and(filter::Header("content-type", "text/plain")),
/// ) /// )
/// .f(|_| HttpResponse::MethodNotAllowed()) /// .to(|| HttpResponse::MethodNotAllowed()))
/// }); /// });
/// } /// }
/// ``` /// ```
@ -191,137 +190,146 @@ impl Filter for HeaderFilter {
} }
} }
/// Return predicate that matches if request contains specified Host name. // /// Return predicate that matches if request contains specified Host name.
/// // ///
/// ```rust,ignore // /// ```rust,ignore
/// # extern crate actix_web; // /// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse}; // /// use actix_web::{pred, App, HttpResponse};
/// // ///
/// fn main() { // /// fn main() {
/// App::new().resource("/index.html", |r| { // /// App::new().resource("/index.html", |r| {
/// r.route() // /// r.route()
/// .filter(pred::Host("www.rust-lang.org")) // /// .filter(pred::Host("www.rust-lang.org"))
/// .f(|_| HttpResponse::MethodNotAllowed()) // /// .f(|_| HttpResponse::MethodNotAllowed())
/// }); // /// });
/// } // /// }
/// ``` // /// ```
pub fn Host<H: AsRef<str>>(host: H) -> HostFilter { // pub fn Host<H: AsRef<str>>(host: H) -> HostFilter {
HostFilter(host.as_ref().to_string(), None) // HostFilter(host.as_ref().to_string(), None)
} // }
#[doc(hidden)] // #[doc(hidden)]
pub struct HostFilter(String, Option<String>); // pub struct HostFilter(String, Option<String>);
impl HostFilter { // impl HostFilter {
/// Set reuest scheme to match // /// Set reuest scheme to match
pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) { // pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
self.1 = Some(scheme.as_ref().to_string()) // self.1 = Some(scheme.as_ref().to_string())
}
}
impl Filter for HostFilter {
fn check(&self, _req: &HttpRequest) -> bool {
// let info = req.connection_info();
// if let Some(ref scheme) = self.1 {
// self.0 == info.host() && scheme == info.scheme()
// } else {
// self.0 == info.host()
// }
false
}
}
// #[cfg(test)]
// mod tests {
// use actix_http::http::{header, Method};
// use actix_http::test::TestRequest;
// use super::*;
// #[test]
// fn test_header() {
// let req = TestRequest::with_header(
// header::TRANSFER_ENCODING,
// header::HeaderValue::from_static("chunked"),
// )
// .finish();
// let pred = Header("transfer-encoding", "chunked");
// assert!(pred.check(&req, req.state()));
// let pred = Header("transfer-encoding", "other");
// assert!(!pred.check(&req, req.state()));
// let pred = Header("content-type", "other");
// assert!(!pred.check(&req, req.state()));
// }
// #[test]
// fn test_host() {
// let req = TestRequest::default()
// .header(
// header::HOST,
// header::HeaderValue::from_static("www.rust-lang.org"),
// )
// .finish();
// let pred = Host("www.rust-lang.org");
// assert!(pred.check(&req, req.state()));
// let pred = Host("localhost");
// assert!(!pred.check(&req, req.state()));
// }
// #[test]
// fn test_methods() {
// let req = TestRequest::default().finish();
// let req2 = TestRequest::default().method(Method::POST).finish();
// assert!(Get().check(&req, req.state()));
// assert!(!Get().check(&req2, req2.state()));
// assert!(Post().check(&req2, req2.state()));
// assert!(!Post().check(&req, req.state()));
// let r = TestRequest::default().method(Method::PUT).finish();
// assert!(Put().check(&r, r.state()));
// assert!(!Put().check(&req, req.state()));
// let r = TestRequest::default().method(Method::DELETE).finish();
// assert!(Delete().check(&r, r.state()));
// assert!(!Delete().check(&req, req.state()));
// let r = TestRequest::default().method(Method::HEAD).finish();
// assert!(Head().check(&r, r.state()));
// assert!(!Head().check(&req, req.state()));
// let r = TestRequest::default().method(Method::OPTIONS).finish();
// assert!(Options().check(&r, r.state()));
// assert!(!Options().check(&req, req.state()));
// let r = TestRequest::default().method(Method::CONNECT).finish();
// assert!(Connect().check(&r, r.state()));
// assert!(!Connect().check(&req, req.state()));
// let r = TestRequest::default().method(Method::PATCH).finish();
// assert!(Patch().check(&r, r.state()));
// assert!(!Patch().check(&req, req.state()));
// let r = TestRequest::default().method(Method::TRACE).finish();
// assert!(Trace().check(&r, r.state()));
// assert!(!Trace().check(&req, req.state()));
// }
// #[test]
// fn test_preds() {
// let r = TestRequest::default().method(Method::TRACE).finish();
// assert!(Not(Get()).check(&r, r.state()));
// assert!(!Not(Trace()).check(&r, r.state()));
// assert!(All(Trace()).and(Trace()).check(&r, r.state()));
// assert!(!All(Get()).and(Trace()).check(&r, r.state()));
// assert!(Any(Get()).or(Trace()).check(&r, r.state()));
// assert!(!Any(Get()).or(Get()).check(&r, r.state()));
// } // }
// } // }
// impl Filter for HostFilter {
// fn check(&self, _req: &HttpRequest) -> bool {
// // let info = req.connection_info();
// // if let Some(ref scheme) = self.1 {
// // self.0 == info.host() && scheme == info.scheme()
// // } else {
// // self.0 == info.host()
// // }
// false
// }
// }
#[cfg(test)]
mod tests {
use crate::test::TestServiceRequest;
use actix_http::http::{header, Method};
use super::*;
#[test]
fn test_header() {
let req = TestServiceRequest::with_header(header::TRANSFER_ENCODING, "chunked")
.finish()
.into_request();
let pred = Header("transfer-encoding", "chunked");
assert!(pred.check(&req));
let pred = Header("transfer-encoding", "other");
assert!(!pred.check(&req));
let pred = Header("content-type", "other");
assert!(!pred.check(&req));
}
// #[test]
// fn test_host() {
// let req = TestServiceRequest::default()
// .header(
// header::HOST,
// header::HeaderValue::from_static("www.rust-lang.org"),
// )
// .request();
// let pred = Host("www.rust-lang.org");
// assert!(pred.check(&req));
// let pred = Host("localhost");
// assert!(!pred.check(&req));
// }
#[test]
fn test_methods() {
let req = TestServiceRequest::default().finish().into_request();
let req2 = TestServiceRequest::default()
.method(Method::POST)
.finish()
.into_request();
assert!(Get().check(&req));
assert!(!Get().check(&req2));
assert!(Post().check(&req2));
assert!(!Post().check(&req));
let r = TestServiceRequest::default().method(Method::PUT).finish();
assert!(Put().check(&r,));
assert!(!Put().check(&req,));
let r = TestServiceRequest::default()
.method(Method::DELETE)
.finish();
assert!(Delete().check(&r,));
assert!(!Delete().check(&req,));
let r = TestServiceRequest::default().method(Method::HEAD).finish();
assert!(Head().check(&r,));
assert!(!Head().check(&req,));
let r = TestServiceRequest::default()
.method(Method::OPTIONS)
.finish();
assert!(Options().check(&r,));
assert!(!Options().check(&req,));
let r = TestServiceRequest::default()
.method(Method::CONNECT)
.finish();
assert!(Connect().check(&r,));
assert!(!Connect().check(&req,));
let r = TestServiceRequest::default().method(Method::PATCH).finish();
assert!(Patch().check(&r,));
assert!(!Patch().check(&req,));
let r = TestServiceRequest::default().method(Method::TRACE).finish();
assert!(Trace().check(&r,));
assert!(!Trace().check(&req,));
}
#[test]
fn test_preds() {
let r = TestServiceRequest::default()
.method(Method::TRACE)
.request();
assert!(Not(Get()).check(&r,));
assert!(!Not(Trace()).check(&r,));
assert!(All(Trace()).and(Trace()).check(&r,));
assert!(!All(Get()).and(Trace()).check(&r,));
assert!(Any(Get()).or(Trace()).check(&r,));
assert!(!Any(Get()).or(Get()).check(&r,));
}
}

View File

@ -13,6 +13,7 @@ mod responder;
mod route; mod route;
mod service; mod service;
mod state; mod state;
pub mod test;
// re-export for convenience // re-export for convenience
pub use actix_http::Response as HttpResponse; pub use actix_http::Response as HttpResponse;

View File

@ -180,7 +180,7 @@ impl<P: 'static> RouteBuilder<P> {
/// # .finish(); /// # .finish();
/// # } /// # }
/// ``` /// ```
pub fn filter<F: Filter + 'static>(&mut self, f: F) -> &mut Self { pub fn filter<F: Filter + 'static>(mut self, f: F) -> Self {
self.filters.push(Box::new(f)); self.filters.push(Box::new(f));
self self
} }

View File

@ -1,18 +1,15 @@
//! Various helpers for Actix applications to use during testing. //! Various helpers for Actix applications to use during testing.
use std::str::FromStr; use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use bytes::Bytes;
use futures::IntoFuture;
use tokio_current_thread::Runtime;
use actix_http::dev::Payload;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use actix_http::http::{HttpTryFrom, Method, Version};
use actix_http::Request as HttpRequest; use actix_http::test::TestRequest;
use actix_http::{Extensions, PayloadStream};
use actix_router::{Path, Url}; use actix_router::{Path, Url};
use bytes::Bytes;
use crate::app::State; use crate::request::HttpRequest;
use crate::request::Request;
use crate::service::ServiceRequest; use crate::service::ServiceRequest;
/// Test `Request` builder /// Test `Request` builder
@ -42,90 +39,71 @@ use crate::service::ServiceRequest;
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
/// } /// }
/// ``` /// ```
pub struct TestRequest<S> { pub struct TestServiceRequest {
state: S, req: TestRequest,
version: Version, extensions: Extensions,
method: Method,
uri: Uri,
headers: HeaderMap,
params: Path<Url>,
payload: Option<Payload>,
} }
impl Default for TestRequest<()> { impl Default for TestServiceRequest {
fn default() -> TestRequest<()> { fn default() -> TestServiceRequest {
TestRequest { TestServiceRequest {
state: (), req: TestRequest::default(),
method: Method::GET, extensions: Extensions::new(),
uri: Uri::from_str("/").unwrap(),
version: Version::HTTP_11,
headers: HeaderMap::new(),
params: Path::new(Url::default()),
payload: None,
} }
} }
} }
impl TestRequest<()> { impl TestServiceRequest {
/// Create TestRequest and set request uri /// Create TestRequest and set request uri
pub fn with_uri(path: &str) -> TestRequest<()> { pub fn with_uri(path: &str) -> TestServiceRequest {
TestRequest::default().uri(path) TestServiceRequest {
req: TestRequest::default().uri(path).take(),
extensions: Extensions::new(),
}
} }
/// Create TestRequest and set header /// Create TestRequest and set header
pub fn with_hdr<H: Header>(hdr: H) -> TestRequest<()> { pub fn with_hdr<H: Header>(hdr: H) -> TestServiceRequest {
TestRequest::default().set(hdr) TestServiceRequest {
req: TestRequest::default().set(hdr).take(),
extensions: Extensions::new(),
}
} }
/// Create TestRequest and set header /// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> TestRequest<()> pub fn with_header<K, V>(key: K, value: V) -> TestServiceRequest
where where
HeaderName: HttpTryFrom<K>, HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
TestRequest::default().header(key, value) TestServiceRequest {
} req: TestRequest::default().header(key, value).take(),
} extensions: Extensions::new(),
impl<S: 'static> TestRequest<S> {
/// Start HttpRequest build process with application state
pub fn with_state(state: S) -> TestRequest<S> {
TestRequest {
state,
method: Method::GET,
uri: Uri::from_str("/").unwrap(),
version: Version::HTTP_11,
headers: HeaderMap::new(),
params: Path::new(Url::default()),
payload: None,
} }
} }
/// Set HTTP version of this request /// Set HTTP version of this request
pub fn version(mut self, ver: Version) -> Self { pub fn version(mut self, ver: Version) -> Self {
self.version = ver; self.req.version(ver);
self self
} }
/// Set HTTP method of this request /// Set HTTP method of this request
pub fn method(mut self, meth: Method) -> Self { pub fn method(mut self, meth: Method) -> Self {
self.method = meth; self.req.method(meth);
self self
} }
/// Set HTTP Uri of this request /// Set HTTP Uri of this request
pub fn uri(mut self, path: &str) -> Self { pub fn uri(mut self, path: &str) -> Self {
self.uri = Uri::from_str(path).unwrap(); self.req.uri(path);
self self
} }
/// Set a header /// Set a header
pub fn set<H: Header>(mut self, hdr: H) -> Self { pub fn set<H: Header>(mut self, hdr: H) -> Self {
if let Ok(value) = hdr.try_into() { self.req.set(hdr);
self.headers.append(H::name(), value); self
return self;
}
panic!("Can not set header");
} }
/// Set a header /// Set a header
@ -134,63 +112,50 @@ impl<S: 'static> TestRequest<S> {
HeaderName: HttpTryFrom<K>, HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
if let Ok(key) = HeaderName::try_from(key) { self.req.header(key, value);
if let Ok(value) = value.try_into() {
self.headers.append(key, value);
return self;
}
}
panic!("Can not create header");
}
/// Set request path pattern parameter
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
self.params.add_static(name, value);
self self
} }
/// Set request payload /// Set request payload
pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self { pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self {
let mut payload = Payload::empty(); self.req.set_payload(data);
payload.unread_data(data.into());
self.payload = Some(payload);
self self
} }
/// Complete request creation and generate `HttpRequest` instance /// Complete request creation and generate `ServiceRequest` instance
pub fn finish(self) -> ServiceRequest<S> { pub fn finish(mut self) -> ServiceRequest<PayloadStream> {
let TestRequest { let req = self.req.finish();
state,
method,
uri,
version,
headers,
mut params,
payload,
} = self;
params.get_mut().update(&uri); ServiceRequest::new(
Path::new(Url::new(req.uri().clone())),
let mut req = HttpRequest::new(); req,
{ Rc::new(self.extensions),
let inner = req.inner_mut(); )
inner.head.uri = uri;
inner.head.method = method;
inner.head.version = version;
inner.head.headers = headers;
*inner.payload.borrow_mut() = payload;
}
Request::new(State::new(state), req, params)
} }
/// This method generates `HttpRequest` instance and executes handler /// Complete request creation and generate `HttpRequest` instance
pub fn run_async<F, R, I, E>(self, f: F) -> Result<I, E> pub fn request(mut self) -> HttpRequest {
where let req = self.req.finish();
F: FnOnce(&Request<S>) -> R,
R: IntoFuture<Item = I, Error = E>, ServiceRequest::new(
{ Path::new(Url::new(req.uri().clone())),
let mut rt = Runtime::new().unwrap(); req,
rt.block_on(f(&self.finish()).into_future()) Rc::new(self.extensions),
)
.into_request()
}
}
impl Deref for TestServiceRequest {
type Target = TestRequest;
fn deref(&self) -> &TestRequest {
&self.req
}
}
impl DerefMut for TestServiceRequest {
fn deref_mut(&mut self) -> &mut TestRequest {
&mut self.req
} }
} }