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"
|
url = "2.1.0"
|
||||||
serde_json = "1.0.44"
|
serde_json = "1.0.44"
|
||||||
serde = "1.0.103"
|
serde = "1.0.103"
|
||||||
|
serde_urlencoded = "0.6.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_env_logger = "0.3.1"
|
pretty_env_logger = "0.3.1"
|
||||||
|
|||||||
@ -7,6 +7,7 @@ pub use crate::all_of;
|
|||||||
pub use crate::any_of;
|
pub use crate::any_of;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
pub mod sequence;
|
||||||
|
|
||||||
pub trait Mapper<IN>: Send + fmt::Debug
|
pub trait Mapper<IN>: Send + fmt::Debug
|
||||||
where
|
where
|
||||||
@ -40,7 +41,10 @@ pub fn any() -> Any {
|
|||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Any;
|
pub struct Any;
|
||||||
impl<IN> Mapper<IN> for Any {
|
impl<IN> Mapper<IN> for Any
|
||||||
|
where
|
||||||
|
IN: ?Sized,
|
||||||
|
{
|
||||||
type Out = bool;
|
type Out = bool;
|
||||||
|
|
||||||
fn map(&mut self, _input: &IN) -> 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 {
|
pub trait IntoRegex {
|
||||||
fn into_regex(self) -> regex::bytes::Regex;
|
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
|
where
|
||||||
C: Mapper<[(String, String)]>,
|
C: Mapper<[(String, String)]>,
|
||||||
{
|
{
|
||||||
UriDecoded(inner)
|
UrlDecoded(inner)
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UriDecoded<C>(C);
|
pub struct UrlDecoded<C>(C);
|
||||||
impl<IN, C> Mapper<IN> for UriDecoded<C>
|
impl<IN, C> Mapper<IN> for UrlDecoded<C>
|
||||||
where
|
where
|
||||||
IN: AsRef<[u8]> + ?Sized,
|
IN: AsRef<[u8]> + ?Sized,
|
||||||
C: Mapper<[(String, String)]>,
|
C: Mapper<[(String, String)]>,
|
||||||
@ -335,12 +356,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_uri_decoded() {
|
fn test_url_decoded() {
|
||||||
let expected = vec![
|
let expected = vec![
|
||||||
("key 1".to_owned(), "value 1".to_owned()),
|
("key 1".to_owned(), "value 1".to_owned()),
|
||||||
("key2".to_owned(), "".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")
|
let req = http::Request::get("https://example.com/path?key%201=value%201&key2")
|
||||||
.body("")
|
.body("")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -88,12 +88,13 @@ pub fn body<C>(inner: C) -> Body<C> {
|
|||||||
pub struct Body<C>(C);
|
pub struct Body<C>(C);
|
||||||
impl<C, B> Mapper<hyper::Request<B>> for Body<C>
|
impl<C, B> Mapper<hyper::Request<B>> for Body<C>
|
||||||
where
|
where
|
||||||
C: Mapper<B>,
|
B: ToOwned,
|
||||||
|
C: Mapper<B::Owned>,
|
||||||
{
|
{
|
||||||
type Out = C::Out;
|
type Out = C::Out;
|
||||||
|
|
||||||
fn map(&mut self, input: &hyper::Request<B>) -> 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>
|
impl<B> Responder for hyper::Response<B>
|
||||||
where
|
where
|
||||||
B: Clone + Into<hyper::Body> + Send + fmt::Debug,
|
B: Clone + Into<hyper::Body> + Send + fmt::Debug,
|
||||||
|
|||||||
@ -109,6 +109,11 @@ impl Server {
|
|||||||
/// all expectations leaving the server running in a clean state.
|
/// all expectations leaving the server running in a clean state.
|
||||||
pub fn verify_and_clear(&mut self) {
|
pub fn verify_and_clear(&mut self) {
|
||||||
let mut state = self.state.lock();
|
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() {
|
for expectation in state.expected.iter() {
|
||||||
let is_valid_cardinality = match &expectation.cardinality {
|
let is_valid_cardinality = match &expectation.cardinality {
|
||||||
Times::AnyNumber => true,
|
Times::AnyNumber => true,
|
||||||
|
|||||||
@ -84,6 +84,7 @@ async fn test_expectation_cardinality_exceeded() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_json() {
|
async fn test_json() {
|
||||||
|
use bstr::B;
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
let my_data = serde_json::json!({
|
let my_data = serde_json::json!({
|
||||||
@ -103,11 +104,16 @@ async fn test_json() {
|
|||||||
.respond_with(json_encoded(my_data.clone())),
|
.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 client = hyper::Client::new();
|
||||||
let resp = read_response_body(client.get(server.url("/foo")).await.unwrap()).await;
|
let resp = read_response_body(client.get(server.url("/foo")).await.unwrap()).await;
|
||||||
assert!(all_of![
|
assert!(all_of![
|
||||||
response::status_code(eq(200)),
|
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))),
|
response::body(json_decoded(eq(my_data))),
|
||||||
]
|
]
|
||||||
.matches(&resp));
|
.matches(&resp));
|
||||||
@ -140,3 +146,45 @@ async fn test_cycle() {
|
|||||||
let resp = read_response_body(client.get(server.url("/foo")).await.unwrap()).await;
|
let resp = read_response_body(client.get(server.url("/foo")).await.unwrap()).await;
|
||||||
assert!(response::status_code(eq(404)).matches(&resp));
|
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