actori_http/
test.rs

1//! Test Various helpers for Actori applications to use during testing.
2use std::convert::TryFrom;
3use std::fmt::Write as FmtWrite;
4use std::io::{self, Read, Write};
5use std::pin::Pin;
6use std::str::FromStr;
7use std::task::{Context, Poll};
8
9use actori_codec::{AsyncRead, AsyncWrite};
10use bytes::{Bytes, BytesMut};
11use http::header::{self, HeaderName, HeaderValue};
12use http::{Error as HttpError, Method, Uri, Version};
13use percent_encoding::percent_encode;
14
15use crate::cookie::{Cookie, CookieJar, USERINFO};
16use crate::header::HeaderMap;
17use crate::header::{Header, IntoHeaderValue};
18use crate::payload::Payload;
19use crate::Request;
20
21/// Test `Request` builder
22///
23/// ```rust,ignore
24/// # use http::{header, StatusCode};
25/// # use actori_web::*;
26/// use actori_web::test::TestRequest;
27///
28/// fn index(req: &HttpRequest) -> Response {
29///     if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
30///         Response::Ok().into()
31///     } else {
32///         Response::BadRequest().into()
33///     }
34/// }
35///
36/// let resp = TestRequest::with_header("content-type", "text/plain")
37///     .run(&index)
38///     .unwrap();
39/// assert_eq!(resp.status(), StatusCode::OK);
40///
41/// let resp = TestRequest::default().run(&index).unwrap();
42/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
43/// ```
44pub struct TestRequest(Option<Inner>);
45
46struct Inner {
47    version: Version,
48    method: Method,
49    uri: Uri,
50    headers: HeaderMap,
51    cookies: CookieJar,
52    payload: Option<Payload>,
53}
54
55impl Default for TestRequest {
56    fn default() -> TestRequest {
57        TestRequest(Some(Inner {
58            method: Method::GET,
59            uri: Uri::from_str("/").unwrap(),
60            version: Version::HTTP_11,
61            headers: HeaderMap::new(),
62            cookies: CookieJar::new(),
63            payload: None,
64        }))
65    }
66}
67
68impl TestRequest {
69    /// Create TestRequest and set request uri
70    pub fn with_uri(path: &str) -> TestRequest {
71        TestRequest::default().uri(path).take()
72    }
73
74    /// Create TestRequest and set header
75    pub fn with_hdr<H: Header>(hdr: H) -> TestRequest {
76        TestRequest::default().set(hdr).take()
77    }
78
79    /// Create TestRequest and set header
80    pub fn with_header<K, V>(key: K, value: V) -> TestRequest
81    where
82        HeaderName: TryFrom<K>,
83        <HeaderName as TryFrom<K>>::Error: Into<HttpError>,
84        V: IntoHeaderValue,
85    {
86        TestRequest::default().header(key, value).take()
87    }
88
89    /// Set HTTP version of this request
90    pub fn version(&mut self, ver: Version) -> &mut Self {
91        parts(&mut self.0).version = ver;
92        self
93    }
94
95    /// Set HTTP method of this request
96    pub fn method(&mut self, meth: Method) -> &mut Self {
97        parts(&mut self.0).method = meth;
98        self
99    }
100
101    /// Set HTTP Uri of this request
102    pub fn uri(&mut self, path: &str) -> &mut Self {
103        parts(&mut self.0).uri = Uri::from_str(path).unwrap();
104        self
105    }
106
107    /// Set a header
108    pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
109        if let Ok(value) = hdr.try_into() {
110            parts(&mut self.0).headers.append(H::name(), value);
111            return self;
112        }
113        panic!("Can not set header");
114    }
115
116    /// Set a header
117    pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
118    where
119        HeaderName: TryFrom<K>,
120        <HeaderName as TryFrom<K>>::Error: Into<HttpError>,
121        V: IntoHeaderValue,
122    {
123        if let Ok(key) = HeaderName::try_from(key) {
124            if let Ok(value) = value.try_into() {
125                parts(&mut self.0).headers.append(key, value);
126                return self;
127            }
128        }
129        panic!("Can not create header");
130    }
131
132    /// Set cookie for this request
133    pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self {
134        parts(&mut self.0).cookies.add(cookie.into_owned());
135        self
136    }
137
138    /// Set request payload
139    pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
140        let mut payload = crate::h1::Payload::empty();
141        payload.unread_data(data.into());
142        parts(&mut self.0).payload = Some(payload.into());
143        self
144    }
145
146    pub fn take(&mut self) -> TestRequest {
147        TestRequest(self.0.take())
148    }
149
150    /// Complete request creation and generate `Request` instance
151    pub fn finish(&mut self) -> Request {
152        let inner = self.0.take().expect("cannot reuse test request builder");
153
154        let mut req = if let Some(pl) = inner.payload {
155            Request::with_payload(pl)
156        } else {
157            Request::with_payload(crate::h1::Payload::empty().into())
158        };
159
160        let head = req.head_mut();
161        head.uri = inner.uri;
162        head.method = inner.method;
163        head.version = inner.version;
164        head.headers = inner.headers;
165
166        let mut cookie = String::new();
167        for c in inner.cookies.delta() {
168            let name = percent_encode(c.name().as_bytes(), USERINFO);
169            let value = percent_encode(c.value().as_bytes(), USERINFO);
170            let _ = write!(&mut cookie, "; {}={}", name, value);
171        }
172        if !cookie.is_empty() {
173            head.headers.insert(
174                header::COOKIE,
175                HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
176            );
177        }
178
179        req
180    }
181}
182
183#[inline]
184fn parts(parts: &mut Option<Inner>) -> &mut Inner {
185    parts.as_mut().expect("cannot reuse test request builder")
186}
187
188/// Async io buffer
189pub struct TestBuffer {
190    pub read_buf: BytesMut,
191    pub write_buf: BytesMut,
192    pub err: Option<io::Error>,
193}
194
195impl TestBuffer {
196    /// Create new TestBuffer instance
197    pub fn new<T>(data: T) -> TestBuffer
198    where
199        BytesMut: From<T>,
200    {
201        TestBuffer {
202            read_buf: BytesMut::from(data),
203            write_buf: BytesMut::new(),
204            err: None,
205        }
206    }
207
208    /// Create new empty TestBuffer instance
209    pub fn empty() -> TestBuffer {
210        TestBuffer::new("")
211    }
212
213    /// Add extra data to read buffer.
214    pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
215        self.read_buf.extend_from_slice(data.as_ref())
216    }
217}
218
219impl io::Read for TestBuffer {
220    fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
221        if self.read_buf.is_empty() {
222            if self.err.is_some() {
223                Err(self.err.take().unwrap())
224            } else {
225                Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
226            }
227        } else {
228            let size = std::cmp::min(self.read_buf.len(), dst.len());
229            let b = self.read_buf.split_to(size);
230            dst[..size].copy_from_slice(&b);
231            Ok(size)
232        }
233    }
234}
235
236impl io::Write for TestBuffer {
237    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
238        self.write_buf.extend(buf);
239        Ok(buf.len())
240    }
241    fn flush(&mut self) -> io::Result<()> {
242        Ok(())
243    }
244}
245
246impl AsyncRead for TestBuffer {
247    fn poll_read(
248        self: Pin<&mut Self>,
249        _: &mut Context<'_>,
250        buf: &mut [u8],
251    ) -> Poll<io::Result<usize>> {
252        Poll::Ready(self.get_mut().read(buf))
253    }
254}
255
256impl AsyncWrite for TestBuffer {
257    fn poll_write(
258        self: Pin<&mut Self>,
259        _: &mut Context<'_>,
260        buf: &[u8],
261    ) -> Poll<io::Result<usize>> {
262        Poll::Ready(self.get_mut().write(buf))
263    }
264
265    fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
266        Poll::Ready(Ok(()))
267    }
268
269    fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
270        Poll::Ready(Ok(()))
271    }
272}