From d49a8ba53bb9f9d6997a7c9d470c18a59c149527 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 21:54:57 -0700 Subject: [PATCH] add client TestResponse --- actix-http/src/h1/payload.rs | 46 +++-------- awc/src/lib.rs | 1 + awc/src/response.rs | 39 +++++++++ awc/src/test.rs | 155 +++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 35 deletions(-) create mode 100644 awc/src/test.rs diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 6665a0e41..979dd015c 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -1,14 +1,14 @@ //! Payload stream -use bytes::{Bytes, BytesMut}; -#[cfg(not(test))] -use futures::task::current as current_task; -use futures::task::Task; -use futures::{Async, Poll, Stream}; use std::cell::RefCell; use std::cmp; use std::collections::VecDeque; use std::rc::{Rc, Weak}; +use bytes::{Bytes, BytesMut}; +use futures::task::current as current_task; +use futures::task::Task; +use futures::{Async, Poll, Stream}; + use crate::error::PayloadError; /// max buffer size 32k @@ -79,11 +79,6 @@ impl Payload { self.inner.borrow_mut().unread_data(data); } - #[cfg(test)] - pub(crate) fn readall(&self) -> Option { - self.inner.borrow_mut().readall() - } - #[inline] /// Set read buffer capacity /// @@ -226,35 +221,16 @@ impl Inner { self.len } - #[cfg(test)] - pub(crate) fn readall(&mut self) -> Option { - let len = self.items.iter().map(|b| b.len()).sum(); - if len > 0 { - let mut buf = BytesMut::with_capacity(len); - for item in &self.items { - buf.extend_from_slice(item); - } - self.items = VecDeque::new(); - self.len = 0; - Some(buf.take().freeze()) - } else { - self.need_read = true; - None - } - } - fn readany(&mut self) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); self.need_read = self.len < self.capacity; - #[cfg(not(test))] - { - if self.need_read && self.task.is_none() { - self.task = Some(current_task()); - } - if let Some(task) = self.io_task.take() { - task.notify() - } + + if self.need_read && self.task.is_none() && !self.eof { + self.task = Some(current_task()); + } + if let Some(task) = self.io_task.take() { + task.notify() } Ok(Async::Ready(Some(data))) } else if let Some(err) = self.err.take() { diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 4898a0627..8ce5b35f7 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -12,6 +12,7 @@ mod builder; mod connect; mod request; mod response; +pub mod test; pub use self::builder::ClientBuilder; pub use self::request::ClientRequest; diff --git a/awc/src/response.rs b/awc/src/response.rs index 5806dc91d..abff771ce 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -249,3 +249,42 @@ where self.poll() } } + +#[cfg(test)] +mod tests { + use super::*; + use futures::Async; + + use crate::{http::header, test::TestResponse}; + + #[test] + fn test_body() { + let req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + match req.body().poll().err().unwrap() { + PayloadError::UnknownLength => (), + _ => unreachable!("error"), + } + + let req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + match req.body().poll().err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } + + let req = TestResponse::default() + .set_payload(Bytes::from_static(b"test")) + .finish(); + match req.body().poll().ok().unwrap() { + Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), + _ => unreachable!("error"), + } + + let req = TestResponse::default() + .set_payload(Bytes::from_static(b"11111111111111")) + .finish(); + match req.body().limit(5).poll().err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } + } +} diff --git a/awc/src/test.rs b/awc/src/test.rs new file mode 100644 index 000000000..464abdd55 --- /dev/null +++ b/awc/src/test.rs @@ -0,0 +1,155 @@ +//! Test Various helpers for Actix applications to use during testing. + +use actix_http::http::header::{Header, IntoHeaderValue}; +use actix_http::http::{HeaderName, HttpTryFrom, Version}; +use actix_http::{h1, Payload, ResponseHead}; +use bytes::Bytes; +#[cfg(feature = "cookies")] +use cookie::{Cookie, CookieJar}; + +use crate::ClientResponse; + +/// Test `ClientResponse` builder +/// +/// ```rust,ignore +/// # extern crate http; +/// # extern crate actix_web; +/// # use http::{header, StatusCode}; +/// # use actix_web::*; +/// use actix_web::test::TestRequest; +/// +/// fn index(req: &HttpRequest) -> Response { +/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { +/// Response::Ok().into() +/// } else { +/// Response::BadRequest().into() +/// } +/// } +/// +/// fn main() { +/// let resp = TestRequest::with_header("content-type", "text/plain") +/// .run(&index) +/// .unwrap(); +/// assert_eq!(resp.status(), StatusCode::OK); +/// +/// let resp = TestRequest::default().run(&index).unwrap(); +/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); +/// } +/// ``` +pub struct TestResponse(Option); + +struct Inner { + head: ResponseHead, + #[cfg(feature = "cookies")] + cookies: CookieJar, + payload: Option, +} + +impl Default for TestResponse { + fn default() -> TestResponse { + TestResponse(Some(Inner { + head: ResponseHead::default(), + #[cfg(feature = "cookies")] + cookies: CookieJar::new(), + payload: None, + })) + } +} + +impl TestResponse { + /// Create TestRequest and set header + pub fn with_header(key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + Self::default().header(key, value).take() + } + + /// Set HTTP version of this request + pub fn version(&mut self, ver: Version) -> &mut Self { + parts(&mut self.0).head.version = ver; + self + } + + /// Set a header + pub fn set(&mut self, hdr: H) -> &mut Self { + if let Ok(value) = hdr.try_into() { + parts(&mut self.0).head.headers.append(H::name(), value); + return self; + } + panic!("Can not set header"); + } + + /// Set a header + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Ok(key) = HeaderName::try_from(key) { + if let Ok(value) = value.try_into() { + parts(&mut self.0).head.headers.append(key, value); + return self; + } + } + panic!("Can not create header"); + } + + /// Set cookie for this request + #[cfg(feature = "cookies")] + pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { + parts(&mut self.0).cookies.add(cookie.into_owned()); + self + } + + /// Set request payload + pub fn set_payload>(&mut self, data: B) -> &mut Self { + let mut payload = h1::Payload::empty(); + payload.unread_data(data.into()); + parts(&mut self.0).payload = Some(payload.into()); + self + } + + pub fn take(&mut self) -> Self { + Self(self.0.take()) + } + + /// Complete request creation and generate `Request` instance + pub fn finish(&mut self) -> ClientResponse { + let inner = self.0.take().expect("cannot reuse test request builder");; + let mut head = inner.head; + + #[cfg(feature = "cookies")] + { + use std::fmt::Write as FmtWrite; + + use actix_http::http::header::{self, HeaderValue}; + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + + let mut cookie = String::new(); + for c in inner.cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + if !cookie.is_empty() { + head.headers.insert( + header::SET_COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } + } + + if let Some(pl) = inner.payload { + ClientResponse::new(head, pl) + } else { + ClientResponse::new(head, h1::Payload::empty().into()) + } + } +} + +#[inline] +fn parts<'a>(parts: &'a mut Option) -> &'a mut Inner { + parts.as_mut().expect("cannot reuse test request builder") +}