1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! # httptest
//!
//! Provide convenient mechanism for testing http clients against a locally
//! running http server. The typical usage is as follows:
//!
//! * Start a server
//! * Configure the server by adding expectations
//! * Test your http client by making requests to the server
//! * On Drop the server verifies all expectations were met.
//!
//! ## Example Test
//!
//! ```
//! # async fn foo() {
//! use httptest::{Server, Expectation, Times, mappers::*, responders::*};
//! // Start a server running on a local ephemeral port.
//! let server = Server::run();
//! // Configure the server to expect a single GET /foo request and respond
//! // with a 200 status code.
//! server.expect(
//!     Expectation::matching(all_of![
//!         request::method(eq("GET")),
//!         request::path(eq("/foo"))
//!     ])
//!     .times(Times::Exactly(1))
//!     .respond_with(status_code(200)),
//! );
//!
//! // The server provides server.addr() that returns the address of the
//! // locally running server, or more conveniently provides a server.url() method
//! // that gives a fully formed http url to the provided path.
//! let url = server.url("/foo");
//! let client = hyper::Client::new();
//! // Issue the GET /foo to the server.
//! let resp = client.get(url).await.unwrap();
//!
//! // Use response matchers to assert the response has a 200 status code.
//! assert!(response::status_code(eq(200)).matches(&resp));
//!
//! // on Drop the server will assert all expectations have been met and will
//! // panic if not.
//! # }
//! ```
//!
//! # Server behavior
//!
//! The Server is started with [run()](struct.Server.html#method.run).
//!
//! The server will run in a background thread until it's dropped. Once dropped
//! it will assert that every configured expectation has been met or will panic.
//! You can also use [verify_and_clear()](struct.Server.html#method.verify_and_clear)
//! to assert and clear the expectations while keeping the server running.
//!
//! [addr()](struct.Server.html#method.addr) will return the address the
//! server is listening on.
//!
//! [url()](struct.Server.html#method.url) will
//! construct a fully formed http url to the path provided i.e.
//!
//! `server.url("/foo?key=value") == "https://<server_addr>/foo?key=value"`.
//!
//! # Defining Expecations
//!
//! Every expecation defines a request matcher, a defintion of the number of
//! times it's expected to be called, and what it should respond with.
//!
//! ### Expectation example
//!
//! ```
//! use httptest::{Expectation, mappers::*, responders::*, Times};
//!
//! // Define an Expectation that matches any request to path /foo, expects to
//! // receive at least 1 such request, and responds with a 200 response.
//! Expectation::matching(request::path(eq("/foo")))
//!     .times(Times::AtLeast(1))
//!     .respond_with(status_code(200));
//! ```
//!
//! ## Request Matchers
//!
//! Defining which request an expecation matches is done in a composable manner
//! using a series of traits. The core of which is
//! [Mapper](mappers/trait.Mapper.html). The `Mapper` trait is generic
//! over an input type, has an associated `Out` type, and defines a single method
//! `map` that converts from a shared reference of the input type to the `Out`
//! type.
//!
//! There's a specialized form of a Mapper where the `Out` type is a boolean.
//! Any `Mapper` that outputs a boolean value is considered a Matcher and
//! implements the [Matcher](mapper/trait.Matcher.html) trait as well. The
//! Matcher trait simply provides a `matches` method.
//!
//! A request matcher is any `Matcher` that takes accepts a
//! `http::Request<hyper::body::Bytes>` as input.
//!
//! With that understanding we can discuss how to easily define a request
//! matcher. There are a variety of pre-defined mappers within the
//! [mappers](mappers/index.html) module. These mappers can be composed
//! together to define the values you want to match. The mappers fall into two
//! categories. Some of the mappers extract a value from the input type and pass
//! it to another mapper, other mappers accept an input type and return a bool.
//! These primitives provide an easy and flexible way to define custom logic.
//!
//! ### Matcher examples
//!
//! ```
//! // pull all the predefined mappers into our namespace.
//! use httptest::mappers::*;
//!
//! // A mapper that returns true when the input equals "/foo"
//! let mut m = eq("/foo");
//!
//! // A mapper that returns true when the input matches the regex "(foo|bar).*"
//! let mut m = matches("(foo|bar).*");
//!
//! // A request matcher that matches a request to path "/foo"
//! let mut m = request::path(eq("/foo"));
//!
//! // A request matcher that matches a POST request
//! let mut m = request::method(eq("POST"));
//!
//! // A request matcher that matches a POST with a path that matches the regex 'foo.*'
//! let mut m = all_of![
//!     request::method(eq("POST")),
//!     request::path(matches("foo.*")),
//! ];
//!
//! # // Allow type inference to determine the request type.
//! # m.map(&http::Request::get("/").body("").unwrap());
//! ```
//!
//! ## Times
//!
//! Each expectation defines how many times a matching request is expected to
//! be received. The [Times](enum.Times.html) enum defines the possibility.
//! `Times::Exactly(1)` is the default value of an `Expectation` if one is not
//! specified with the
//! [times()](struct.ExpectationBuilder.html#method.times) method.
//!
//! The server will respond to any requests that violate the times request with
//! a 500 status code and the server will subsequently panic on Drop.
//!
//! ## Responder
//!
//! Responders define how the server will respond to a matched request. There
//! are a number of implemented responders within the responders module. In
//! addition to the predefined responders you can provide any
//! `http::Response` with a body that can be cloned or implement your own
//! Responder.
//!
//! ## Responder example
//!
//! ```
//! use httptest::responders::*;
//!
//! // respond with a successful 200 status code.
//! status_code(200);
//!
//! // respond with a 404 page not found.
//! status_code(404);
//!
//! // respond with a json encoded body.
//! json_encoded(serde_json::json!({
//!     "my_key": 100,
//!     "my_key2": [1, 2, "foo", 99],
//! }));
//!
//! // alternate between responding with a 200 and a 404.
//! cycle![
//!     status_code(200),
//!     status_code(404),
//! ];
//!
//! ```

#![deny(missing_docs)]

/// true if all the provided matchers return true.
///
/// The macro exists to conveniently box a list of mappers and put them into a
/// `Vec<Box<dyn Mapper>>`. The translation is:
///
/// `all_of![a, b] => all_of(vec![Box::new(a), Box::new(b)])`
#[macro_export]
macro_rules! all_of {
    ($($x:expr),*) => ($crate::mappers::all_of($crate::vec_of_boxes![$($x),*]));
    ($($x:expr,)*) => ($crate::all_of![$($x),*]);
}

/// true if any of the provided matchers return true.
///
/// The macro exists to conveniently box a list of mappers and put them into a
/// `Vec<Box<dyn Mapper>>`. The translation is:
///
/// `any_of![a, b] => any_of(vec![Box::new(a), Box::new(b)])`
#[macro_export]
macro_rules! any_of {
    ($($x:expr),*) => ($crate::mappers::any_of($crate::vec_of_boxes![$($x),*]));
    ($($x:expr,)*) => ($crate::any_of![$($x),*]);
}

/// a Responder that cycles through a list of responses.
///
/// The macro exists to conveniently box a list of responders and put them into a
/// `Vec<Box<dyn Responder>>`. The translation is:
///
/// `cycle![a, b] => cycle(vec![Box::new(a), Box::new(b)])`
#[macro_export]
macro_rules! cycle {
    ($($x:expr),*) => ($crate::responders::cycle($crate::vec_of_boxes![$($x),*]));
    ($($x:expr,)*) => ($crate::cycle![$($x),*]);
}

// hidden from docs because it's an implementation detail of the above macros.
#[doc(hidden)]
#[macro_export]
macro_rules! vec_of_boxes {
    ($($x:expr),*) => (std::vec![$(std::boxed::Box::new($x)),*]);
    ($($x:expr,)*) => ($crate::vec_of_boxes![$($x),*]);
}

pub mod mappers;
pub mod responders;
pub use httptest_server::{Expectation, Times, Server};
//mod server;

//pub use server::{Expectation, ExpectationBuilder, Server, Times};