Added Url encoding and fixed bugs that cropped up in the process.
This commit is contained in:
parent
1269db042b
commit
1922c99979
@ -18,6 +18,7 @@ regex = "1.3.1"
|
||||
url = "2.1.0"
|
||||
serde_json = "1.0.44"
|
||||
serde = "1.0.103"
|
||||
serde_urlencoded = "0.6.1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_env_logger = "0.3.1"
|
||||
|
||||
@ -7,6 +7,7 @@ pub use crate::all_of;
|
||||
pub use crate::any_of;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod sequence;
|
||||
|
||||
pub trait Mapper<IN>: Send + fmt::Debug
|
||||
where
|
||||
@ -40,7 +41,10 @@ pub fn any() -> Any {
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct Any;
|
||||
impl<IN> Mapper<IN> for Any {
|
||||
impl<IN> Mapper<IN> for Any
|
||||
where
|
||||
IN: ?Sized,
|
||||
{
|
||||
type Out = bool;
|
||||
|
||||
fn map(&mut self, _input: &IN) -> bool {
|
||||
@ -65,6 +69,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deref<C>(inner: C) -> Deref<C> {
|
||||
Deref(inner)
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct Deref<C>(C);
|
||||
impl<C, IN> Mapper<IN> for Deref<C>
|
||||
where
|
||||
C: Mapper<IN::Target>,
|
||||
IN: std::ops::Deref,
|
||||
{
|
||||
type Out = C::Out;
|
||||
|
||||
fn map(&mut self, input: &IN) -> C::Out {
|
||||
self.0.map(input.deref())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoRegex {
|
||||
fn into_regex(self) -> regex::bytes::Regex;
|
||||
}
|
||||
@ -173,15 +194,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uri_decoded<C>(inner: C) -> UriDecoded<C>
|
||||
pub fn url_decoded<C>(inner: C) -> UrlDecoded<C>
|
||||
where
|
||||
C: Mapper<[(String, String)]>,
|
||||
{
|
||||
UriDecoded(inner)
|
||||
UrlDecoded(inner)
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct UriDecoded<C>(C);
|
||||
impl<IN, C> Mapper<IN> for UriDecoded<C>
|
||||
pub struct UrlDecoded<C>(C);
|
||||
impl<IN, C> Mapper<IN> for UrlDecoded<C>
|
||||
where
|
||||
IN: AsRef<[u8]> + ?Sized,
|
||||
C: Mapper<[(String, String)]>,
|
||||
@ -335,12 +356,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uri_decoded() {
|
||||
fn test_url_decoded() {
|
||||
let expected = vec![
|
||||
("key 1".to_owned(), "value 1".to_owned()),
|
||||
("key2".to_owned(), "".to_owned()),
|
||||
];
|
||||
let mut c = request::query(uri_decoded(eq(expected)));
|
||||
let mut c = request::query(url_decoded(eq(expected)));
|
||||
let req = http::Request::get("https://example.com/path?key%201=value%201&key2")
|
||||
.body("")
|
||||
.unwrap();
|
||||
|
||||
@ -88,12 +88,13 @@ pub fn body<C>(inner: C) -> Body<C> {
|
||||
pub struct Body<C>(C);
|
||||
impl<C, B> Mapper<hyper::Request<B>> for Body<C>
|
||||
where
|
||||
C: Mapper<B>,
|
||||
B: ToOwned,
|
||||
C: Mapper<B::Owned>,
|
||||
{
|
||||
type Out = C::Out;
|
||||
|
||||
fn map(&mut self, input: &hyper::Request<B>) -> C::Out {
|
||||
self.0.map(input.body())
|
||||
self.0.map(&input.body().to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,27 @@ impl Responder for JsonEncoded {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url_encoded<T>(data: T) -> impl Responder
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
UrlEncoded(serde_urlencoded::to_string(&data).unwrap())
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct UrlEncoded(String);
|
||||
impl Responder for UrlEncoded {
|
||||
fn respond(&mut self) -> Pin<Box<dyn Future<Output = http::Response<hyper::Body>> + Send>> {
|
||||
async fn _respond(body: String) -> http::Response<hyper::Body> {
|
||||
hyper::Response::builder()
|
||||
.status(200)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.body(body.into())
|
||||
.unwrap()
|
||||
}
|
||||
Box::pin(_respond(self.0.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Responder for hyper::Response<B>
|
||||
where
|
||||
B: Clone + Into<hyper::Body> + Send + fmt::Debug,
|
||||
|
||||
@ -109,6 +109,11 @@ impl Server {
|
||||
/// all expectations leaving the server running in a clean state.
|
||||
pub fn verify_and_clear(&mut self) {
|
||||
let mut state = self.state.lock();
|
||||
if std::thread::panicking() {
|
||||
// If the test is already panicking don't double panic on drop.
|
||||
state.expected.clear();
|
||||
return;
|
||||
}
|
||||
for expectation in state.expected.iter() {
|
||||
let is_valid_cardinality = match &expectation.cardinality {
|
||||
Times::AnyNumber => true,
|
||||
|
||||
@ -84,6 +84,7 @@ async fn test_expectation_cardinality_exceeded() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_json() {
|
||||
use bstr::B;
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
let my_data = serde_json::json!({
|
||||
@ -103,11 +104,16 @@ async fn test_json() {
|
||||
.respond_with(json_encoded(my_data.clone())),
|
||||
);
|
||||
|
||||
// Issue the GET /foo to the server and verify it returns a 200.
|
||||
// Issue the GET /foo to the server and verify it returns a 200 with a json
|
||||
// body matching my_data.
|
||||
let client = hyper::Client::new();
|
||||
let resp = read_response_body(client.get(server.url("/foo")).await.unwrap()).await;
|
||||
assert!(all_of![
|
||||
response::status_code(eq(200)),
|
||||
response::headers(sequence::contains((
|
||||
deref(eq(B("content-type"))),
|
||||
deref(eq(B("application/json"))),
|
||||
))),
|
||||
response::body(json_decoded(eq(my_data))),
|
||||
]
|
||||
.matches(&resp));
|
||||
@ -140,3 +146,45 @@ async fn test_cycle() {
|
||||
let resp = read_response_body(client.get(server.url("/foo")).await.unwrap()).await;
|
||||
assert!(response::status_code(eq(404)).matches(&resp));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_url_encoded() {
|
||||
use bstr::B;
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
// Setup a server to expect a single GET /foo request and respond with a
|
||||
// json response.
|
||||
let my_data = vec![("key", "value")];
|
||||
let server = httptest::Server::run();
|
||||
server.expect(
|
||||
Expectation::matching(all_of![
|
||||
request::method(eq("GET")),
|
||||
request::path(eq("/foo")),
|
||||
request::query(url_decoded(sequence::contains((
|
||||
deref(eq("key")),
|
||||
deref(eq("value")),
|
||||
)))),
|
||||
])
|
||||
.times(Times::Exactly(1))
|
||||
.respond_with(url_encoded(my_data.clone())),
|
||||
);
|
||||
|
||||
// Issue the GET /foo?key=value to the server and verify it returns a 200 with an
|
||||
// application/x-www-form-urlencoded body of key=value.
|
||||
let client = hyper::Client::new();
|
||||
let resp = read_response_body(client.get(server.url("/foo?key=value")).await.unwrap()).await;
|
||||
assert!(all_of![
|
||||
response::status_code(eq(200)),
|
||||
response::headers(sequence::contains((
|
||||
deref(eq(B("content-type"))),
|
||||
deref(eq(B("application/x-www-form-urlencoded"))),
|
||||
))),
|
||||
response::body(url_decoded(sequence::contains((
|
||||
deref(eq("key")),
|
||||
deref(eq("value"))
|
||||
)))),
|
||||
]
|
||||
.matches(&resp));
|
||||
|
||||
// The Drop impl of the server will assert that all expectations were satisfied or else it will panic.
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user