From e4198a037a9d42d1546791c62f3605dec5909dab Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 16:24:14 -0800 Subject: [PATCH] add TestServiceRequest builder --- Cargo.toml | 11 +- src/app.rs | 13 ++- src/filter.rs | 288 ++++++++++++++++++++++++++------------------------ src/lib.rs | 1 + src/route.rs | 2 +- src/test.rs | 173 ++++++++++++------------------ 6 files changed, 229 insertions(+), 259 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5b2fa3f..30d23d02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,13 +44,11 @@ flate2-rust = ["flate2/rust_backend"] [dependencies] actix-codec = "0.1.0" -#actix-service = "0.2.1" -#actix-server = "0.2.1" -#actix-utils = "0.2.1" -actix-utils = { git = "https://github.com/actix/actix-net.git" } +actix-service = "0.3.0" +actix-utils = "0.3.0" + actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } -actix-service = { git = "https://github.com/actix/actix-net.git" } bytes = "0.4" derive_more = "0.14" @@ -73,8 +71,7 @@ flate2 = { version="^1.0.2", optional = true, default-features = false } [dev-dependencies] actix-rt = "0.1.0" -#actix-server = { version="0.2", features=["ssl"] } -actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] } +actix-server = { version="0.3.0", 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"] } rand = "0.6" diff --git a/src/app.rs b/src/app.rs index 78f718db..2e45f2d3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; -use actix_http::body::MessageBody; +use actix_http::body::{Body, MessageBody}; use actix_http::{Extensions, PayloadStream, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; @@ -130,7 +130,7 @@ where /// }); /// } /// ``` - pub fn resource(self, path: &str, f: F) -> AppRouter> + pub fn resource(self, path: &str, f: F) -> AppRouter> where F: FnOnce(Resource

) -> Resource, U: NewService< @@ -159,16 +159,16 @@ where } /// Register a middleware. - pub fn middleware( + pub fn middleware( self, mw: F, ) -> AppRouter< T, P, - B, + Body, impl NewService< Request = ServiceRequest

, - Response = ServiceResponse, + Response = ServiceResponse, Error = (), InitError = (), >, @@ -177,11 +177,10 @@ where M: NewTransform< AppService

, Request = ServiceRequest

, - Response = ServiceResponse, + Response = ServiceResponse, Error = (), InitError = (), >, - B: MessageBody, F: IntoNewTransform>, { let fref = Rc::new(RefCell::new(None)); diff --git a/src/filter.rs b/src/filter.rs index a0566092..37c23d73 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -16,14 +16,13 @@ pub trait Filter { /// Return filter that matches if any of supplied filters. /// /// ```rust,ignore -/// # extern crate actix_web; -/// use actix_web2::{filter, App, HttpResponse}; +/// use actix_web::{filter, App, HttpResponse}; /// /// fn main() { /// App::new().resource("/index.html", |r| { /// r.route() /// .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. /// -/// ```rust,ignore +/// ```rust /// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; +/// use actix_web::{filter, App, HttpResponse}; /// /// fn main() { /// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter( -/// pred::All(pred::Get()) -/// .and(pred::Header("content-type", "text/plain")), +/// r.route( +/// |r| r.filter( +/// filter::All(filter::Get()) +/// .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. -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Host("www.rust-lang.org")) -/// .f(|_| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn Host>(host: H) -> HostFilter { - HostFilter(host.as_ref().to_string(), None) -} +// /// Return predicate that matches if request contains specified Host name. +// /// +// /// ```rust,ignore +// /// # extern crate actix_web; +// /// use actix_web::{pred, App, HttpResponse}; +// /// +// /// fn main() { +// /// App::new().resource("/index.html", |r| { +// /// r.route() +// /// .filter(pred::Host("www.rust-lang.org")) +// /// .f(|_| HttpResponse::MethodNotAllowed()) +// /// }); +// /// } +// /// ``` +// pub fn Host>(host: H) -> HostFilter { +// HostFilter(host.as_ref().to_string(), None) +// } -#[doc(hidden)] -pub struct HostFilter(String, Option); +// #[doc(hidden)] +// pub struct HostFilter(String, Option); -impl HostFilter { - /// Set reuest scheme to match - pub fn scheme>(&mut self, scheme: H) { - 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 HostFilter { +// /// Set reuest scheme to match +// pub fn scheme>(&mut self, scheme: H) { +// 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 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,)); + } +} diff --git a/src/lib.rs b/src/lib.rs index c70f47e8..70ce9607 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ mod responder; mod route; mod service; mod state; +pub mod test; // re-export for convenience pub use actix_http::Response as HttpResponse; diff --git a/src/route.rs b/src/route.rs index 4abb2f1d..d5e11424 100644 --- a/src/route.rs +++ b/src/route.rs @@ -180,7 +180,7 @@ impl RouteBuilder

{ /// # .finish(); /// # } /// ``` - pub fn filter(&mut self, f: F) -> &mut Self { + pub fn filter(mut self, f: F) -> Self { self.filters.push(Box::new(f)); self } diff --git a/src/test.rs b/src/test.rs index e13dcd16..d67696b1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,18 +1,15 @@ //! 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::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use actix_http::Request as HttpRequest; +use actix_http::http::{HttpTryFrom, Method, Version}; +use actix_http::test::TestRequest; +use actix_http::{Extensions, PayloadStream}; use actix_router::{Path, Url}; +use bytes::Bytes; -use crate::app::State; -use crate::request::Request; +use crate::request::HttpRequest; use crate::service::ServiceRequest; /// Test `Request` builder @@ -42,90 +39,71 @@ use crate::service::ServiceRequest; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` -pub struct TestRequest { - state: S, - version: Version, - method: Method, - uri: Uri, - headers: HeaderMap, - params: Path, - payload: Option, +pub struct TestServiceRequest { + req: TestRequest, + extensions: Extensions, } -impl Default for TestRequest<()> { - fn default() -> TestRequest<()> { - TestRequest { - state: (), - method: Method::GET, - uri: Uri::from_str("/").unwrap(), - version: Version::HTTP_11, - headers: HeaderMap::new(), - params: Path::new(Url::default()), - payload: None, +impl Default for TestServiceRequest { + fn default() -> TestServiceRequest { + TestServiceRequest { + req: TestRequest::default(), + extensions: Extensions::new(), } } } -impl TestRequest<()> { +impl TestServiceRequest { /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestRequest<()> { - TestRequest::default().uri(path) + pub fn with_uri(path: &str) -> TestServiceRequest { + TestServiceRequest { + req: TestRequest::default().uri(path).take(), + extensions: Extensions::new(), + } } /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest<()> { - TestRequest::default().set(hdr) + pub fn with_hdr(hdr: H) -> TestServiceRequest { + TestServiceRequest { + req: TestRequest::default().set(hdr).take(), + extensions: Extensions::new(), + } } /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest<()> + pub fn with_header(key: K, value: V) -> TestServiceRequest where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - TestRequest::default().header(key, value) - } -} - -impl TestRequest { - /// Start HttpRequest build process with application state - pub fn with_state(state: S) -> TestRequest { - TestRequest { - state, - method: Method::GET, - uri: Uri::from_str("/").unwrap(), - version: Version::HTTP_11, - headers: HeaderMap::new(), - params: Path::new(Url::default()), - payload: None, + TestServiceRequest { + req: TestRequest::default().header(key, value).take(), + extensions: Extensions::new(), } } /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { - self.version = ver; + self.req.version(ver); self } /// Set HTTP method of this request pub fn method(mut self, meth: Method) -> Self { - self.method = meth; + self.req.method(meth); self } /// Set HTTP Uri of this request pub fn uri(mut self, path: &str) -> Self { - self.uri = Uri::from_str(path).unwrap(); + self.req.uri(path); self } /// Set a header pub fn set(mut self, hdr: H) -> Self { - if let Ok(value) = hdr.try_into() { - self.headers.append(H::name(), value); - return self; - } - panic!("Can not set header"); + self.req.set(hdr); + self } /// Set a header @@ -134,63 +112,50 @@ impl TestRequest { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Ok(key) = HeaderName::try_from(key) { - 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.req.header(key, value); self } /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { - let mut payload = Payload::empty(); - payload.unread_data(data.into()); - self.payload = Some(payload); + self.req.set_payload(data); self } - /// Complete request creation and generate `HttpRequest` instance - pub fn finish(self) -> ServiceRequest { - let TestRequest { - state, - method, - uri, - version, - headers, - mut params, - payload, - } = self; + /// Complete request creation and generate `ServiceRequest` instance + pub fn finish(mut self) -> ServiceRequest { + let req = self.req.finish(); - params.get_mut().update(&uri); - - let mut req = HttpRequest::new(); - { - 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) + ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + Rc::new(self.extensions), + ) } - /// This method generates `HttpRequest` instance and executes handler - pub fn run_async(self, f: F) -> Result - where - F: FnOnce(&Request) -> R, - R: IntoFuture, - { - let mut rt = Runtime::new().unwrap(); - rt.block_on(f(&self.finish()).into_future()) + /// Complete request creation and generate `HttpRequest` instance + pub fn request(mut self) -> HttpRequest { + let req = self.req.finish(); + + ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + 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 } }