From 5cfba5ff165bf5452e36afbbd49725943bcc4c60 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 12 Apr 2019 11:15:58 -0700 Subject: [PATCH] add FramedRequest builder for testing --- actix-framed/src/lib.rs | 1 + actix-framed/src/request.rs | 12 ++- actix-framed/src/test.rs | 133 ++++++++++++++++++++++++++++++ actix-framed/tests/test_server.rs | 55 +++++++++++- src/test.rs | 35 ++------ test-server/src/lib.rs | 36 ++++++++ 6 files changed, 240 insertions(+), 32 deletions(-) create mode 100644 actix-framed/src/test.rs diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs index 62b0cf1ec..c6405e20b 100644 --- a/actix-framed/src/lib.rs +++ b/actix-framed/src/lib.rs @@ -4,6 +4,7 @@ mod request; mod route; mod service; mod state; +pub mod test; // re-export for convinience pub use actix_http::{http, Error, HttpMessage, Response, ResponseError}; diff --git a/actix-framed/src/request.rs b/actix-framed/src/request.rs index eab28a9ef..bdcdd7026 100644 --- a/actix-framed/src/request.rs +++ b/actix-framed/src/request.rs @@ -123,6 +123,7 @@ impl FramedRequest { #[cfg(test)] mod tests { + use actix_http::http::{HeaderName, HeaderValue, HttpTryFrom}; use actix_http::test::{TestBuffer, TestRequest}; use super::*; @@ -136,7 +137,7 @@ mod tests { .finish(); let path = Path::new(Url::new(req.uri().clone())); - let freq = FramedRequest::new(req, framed, path, State::new(10u8)); + let mut freq = FramedRequest::new(req, framed, path, State::new(10u8)); assert_eq!(*freq.state(), 10); assert_eq!(freq.version(), Version::HTTP_11); assert_eq!(freq.method(), Method::GET); @@ -151,6 +152,15 @@ mod tests { "test" ); + freq.head_mut().headers.insert( + HeaderName::try_from("x-hdr").unwrap(), + HeaderValue::from_static("test"), + ); + assert_eq!( + freq.headers().get("x-hdr").unwrap().to_str().unwrap(), + "test" + ); + freq.extensions_mut().insert(100usize); assert_eq!(*freq.extensions().get::().unwrap(), 100usize); diff --git a/actix-framed/src/test.rs b/actix-framed/src/test.rs new file mode 100644 index 000000000..c890662ef --- /dev/null +++ b/actix-framed/src/test.rs @@ -0,0 +1,133 @@ +//! Various helpers for Actix applications to use during testing. +use actix_codec::Framed; +use actix_http::h1::Codec; +use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; +use actix_http::http::{HttpTryFrom, Method, Uri, Version}; +use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest}; +use actix_router::{Path, Url}; + +use crate::{FramedRequest, State}; + +/// Test `Request` builder. +pub struct TestRequest { + req: HttpTestRequest, + path: Path, +} + +impl Default for TestRequest { + fn default() -> TestRequest { + TestRequest { + req: HttpTestRequest::default(), + path: Path::new(Url::new(Uri::default())), + } + } +} + +#[allow(clippy::wrong_self_convention)] +impl TestRequest { + /// Create TestRequest and set request uri + pub fn with_uri(path: &str) -> TestRequest { + Self::get().uri(path) + } + + /// Create TestRequest and set header + pub fn with_hdr(hdr: H) -> TestRequest { + Self::default().set(hdr) + } + + /// Create TestRequest and set header + pub fn with_header(key: K, value: V) -> TestRequest + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + Self::default().header(key, value) + } + + /// Create TestRequest and set method to `Method::GET` + pub fn get() -> TestRequest { + Self::default().method(Method::GET) + } + + /// Create TestRequest and set method to `Method::POST` + pub fn post() -> TestRequest { + Self::default().method(Method::POST) + } + + /// Set HTTP version of this request + pub fn version(mut self, ver: Version) -> Self { + self.req.version(ver); + self + } + + /// Set HTTP method of this request + pub fn method(mut self, meth: Method) -> Self { + self.req.method(meth); + self + } + + /// Set HTTP Uri of this request + pub fn uri(mut self, path: &str) -> Self { + self.req.uri(path); + self + } + + /// Set a header + pub fn set(mut self, hdr: H) -> Self { + self.req.set(hdr); + self + } + + /// Set a header + pub fn header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + self.req.header(key, value); + self + } + + /// Set request path pattern parameter + pub fn param(mut self, name: &'static str, value: &'static str) -> Self { + self.path.add_static(name, value); + self + } + + /// Complete request creation and generate `Request` instance + pub fn finish(self) -> FramedRequest { + self.finish_with_state(()) + } + + /// Complete request creation and generate `Request` instance + pub fn finish_with_state(mut self, state: S) -> FramedRequest { + let req = self.req.finish(); + self.path.get_mut().update(req.uri()); + let framed = Framed::new(TestBuffer::empty(), Codec::default()); + FramedRequest::new(req, framed, self.path, State::new(state)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let req = TestRequest::with_uri("/index.html") + .header("x-test", "test") + .param("test", "123") + .finish(); + + assert_eq!(*req.state(), ()); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(req.method(), Method::GET); + assert_eq!(req.path(), "/index.html"); + assert_eq!(req.query_string(), ""); + assert_eq!( + req.headers().get("x-test").unwrap().to_str().unwrap(), + "test" + ); + assert_eq!(&req.match_info()["test"], "123"); + } +} diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs index 5b85f3128..964403e15 100644 --- a/actix-framed/tests/test_server.rs +++ b/actix-framed/tests/test_server.rs @@ -1,12 +1,13 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{body, ws, Error, HttpService, Response}; use actix_http_test::TestServer; +use actix_service::{IntoNewService, NewService}; use actix_utils::framed::FramedTransport; use bytes::{Bytes, BytesMut}; use futures::future::{self, ok}; use futures::{Future, Sink, Stream}; -use actix_framed::{FramedApp, FramedRequest, FramedRoute}; +use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets}; fn ws_service( req: FramedRequest, @@ -81,3 +82,55 @@ fn test_simple() { Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) ); } + +#[test] +fn test_service() { + let mut srv = TestServer::new(|| { + actix_http::h1::OneRequest::new().map_err(|_| ()).and_then( + VerifyWebSockets::default() + .then(SendError::default()) + .map_err(|_| ()) + .and_then( + FramedApp::new() + .service(FramedRoute::get("/index.html").to(ws_service)) + .into_new_service() + .map_err(|_| ()), + ), + ) + }); + + assert!(srv.ws_at("/test").is_err()); + + // client service + let framed = srv.ws_at("/index.html").unwrap(); + let framed = srv + .block_on(framed.send(ws::Message::Text("text".to_string()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + + let framed = srv + .block_on(framed.send(ws::Message::Binary("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) + ); + + let framed = srv + .block_on(framed.send(ws::Message::Ping("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + + let framed = srv + .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) + .unwrap(); + + let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) + ); +} diff --git a/src/test.rs b/src/test.rs index 5b44d1c79..affd80bb7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -201,22 +201,12 @@ impl Default for TestRequest { impl TestRequest { /// Create TestRequest and set request uri pub fn with_uri(path: &str) -> TestRequest { - TestRequest { - req: HttpTestRequest::default().uri(path).take(), - rmap: ResourceMap::new(ResourceDef::new("")), - config: AppConfigInner::default(), - route_data: Extensions::new(), - } + TestRequest::default().uri(path) } /// Create TestRequest and set header pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest { - req: HttpTestRequest::default().set(hdr).take(), - config: AppConfigInner::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - route_data: Extensions::new(), - } + TestRequest::default().set(hdr) } /// Create TestRequest and set header @@ -225,32 +215,17 @@ impl TestRequest { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - TestRequest { - req: HttpTestRequest::default().header(key, value).take(), - config: AppConfigInner::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - route_data: Extensions::new(), - } + TestRequest::default().header(key, value) } /// Create TestRequest and set method to `Method::GET` pub fn get() -> TestRequest { - TestRequest { - req: HttpTestRequest::default().method(Method::GET).take(), - config: AppConfigInner::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - route_data: Extensions::new(), - } + TestRequest::default().method(Method::GET) } /// Create TestRequest and set method to `Method::POST` pub fn post() -> TestRequest { - TestRequest { - req: HttpTestRequest::default().method(Method::POST).take(), - config: AppConfigInner::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - route_data: Extensions::new(), - } + TestRequest::default().method(Method::POST) } /// Set HTTP version of this request diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index d83432df9..37abe1292 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -1,4 +1,5 @@ //! Various helpers for Actix applications to use during testing. +use std::cell::RefCell; use std::sync::mpsc; use std::{net, thread, time}; @@ -12,6 +13,41 @@ use futures::{Future, Stream}; use http::Method; use net2::TcpBuilder; +thread_local! { + static RT: RefCell = { + RefCell::new(Runtime::new().unwrap()) + }; +} + +/// Runs the provided future, blocking the current thread until the future +/// completes. +/// +/// This function can be used to synchronously block the current thread +/// until the provided `future` has resolved either successfully or with an +/// error. The result of the future is then returned from this function +/// call. +/// +/// Note that this function is intended to be used only for testing purpose. +/// This function panics on nested call. +pub fn block_on(f: F) -> Result +where + F: Future, +{ + RT.with(move |rt| rt.borrow_mut().block_on(f)) +} + +/// Runs the provided function, with runtime enabled. +/// +/// Note that this function is intended to be used only for testing purpose. +/// This function panics on nested call. +pub fn run_on(f: F) -> R +where + F: Fn() -> R, +{ + RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f())))) + .unwrap() +} + /// The `TestServer` type. /// /// `TestServer` is very simple test server that simplify process of writing