Skip to content

Commit

Permalink
feat: add option to include empty provider states when using a filter #…
Browse files Browse the repository at this point in the history
  • Loading branch information
Ronald Holshausen committed Jun 12, 2020
1 parent 17e8b3b commit 729674c
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 31 deletions.
11 changes: 9 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,12 @@ async fn handle_command_args() -> Result<(), i32> {
.number_of_values(1)
.empty_values(false)
.help("Name of the header parameter containing the provider state to be used in case \
multiple matching interactions are found"));
multiple matching interactions are found"))
.arg(Arg::with_name("empty-provider-state")
.long("empty-provider-state")
.takes_value(false)
.use_delimiter(false)
.help("Include empty provider states when filtering"));

let matches = app.get_matches_safe();
match matches {
Expand All @@ -360,9 +365,11 @@ async fn handle_command_args() -> Result<(), i32> {
.map(|filter| Regex::new(filter).unwrap());
let provider_state_header_name = matches.value_of("provider-state-header-name")
.map(|filter| String::from(filter));
let empty_provider_states = matches.is_present("empty-provider-state");
let pacts = pacts.iter().cloned().map(|p| p.unwrap()).collect();
let server_handler = ServerHandler::new(pacts, matches.is_present("cors"),
matches.is_present("cors-referer"), provider_state, provider_state_header_name);
matches.is_present("cors-referer"), provider_state, provider_state_header_name,
empty_provider_states);
server_handler.start_server(port)
}
},
Expand Down
101 changes: 72 additions & 29 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ pub struct ServerHandler {
auto_cors: bool,
cors_referer: bool,
provider_state: Option<Regex>,
provider_state_header_name: Option<String>
provider_state_header_name: Option<String>,
empty_provider_states: bool
}

fn method_supports_payload(request: &Request) -> bool {
Expand All @@ -32,17 +33,19 @@ fn method_supports_payload(request: &Request) -> bool {
}
}

fn find_matching_request(request: &Request, auto_cors: bool, cors_referer: bool, sources: &Vec<Pact>, provider_state: Option<Regex>) -> Result<Response, String> {
match provider_state.clone() {
fn find_matching_request(request: &Request, auto_cors: bool, cors_referer: bool, sources: &Vec<Pact>,
provider_state: Option<Regex>, empty_provider_states: bool) -> Result<Response, String> {
match &provider_state {
Some(state) => info!("Filtering interactions by provider state regex '{}'", state),
None => ()
}
let match_results = sources
.iter()
.flat_map(|pact| pact.interactions.clone())
.filter(|i| match provider_state {
Some(ref regex) => i.provider_states.iter().any(|state| regex.is_match(state.name
.as_str())),
Some(ref regex) => empty_provider_states && i.provider_states.is_empty() ||
i.provider_states.iter().any(|state|
empty_provider_states && state.name.is_empty() || regex.is_match(state.name.as_str())),
None => true
})
.map(|i| (i.clone(), pact_matching::match_request(i.request, request.clone())))
Expand Down Expand Up @@ -91,12 +94,14 @@ fn find_matching_request(request: &Request, auto_cors: bool, cors_referer: bool,
}
}

fn handle_request(request: Request, auto_cors: bool, cors_referrer: bool, sources: Vec<Pact>, provider_state: Option<Regex>) -> Response {
fn handle_request(request: Request, auto_cors: bool, cors_referrer: bool, sources: Vec<Pact>,
provider_state: Option<Regex>, empty_provider_states: bool) -> Response {
info! ("===> Received {}", request);
debug!(" body: '{}'", request.body.str_value());
debug!(" matching_rules: {:?}", request.matching_rules);
debug!(" generators: {:?}", request.generators);
match find_matching_request(&request, auto_cors, cors_referrer, &sources, provider_state) {
match find_matching_request(&request, auto_cors, cors_referrer, &sources, provider_state,
empty_provider_states) {
Ok(response) => response,
Err(msg) => {
warn!("{}, sending {}", msg, StatusCode::NOT_FOUND);
Expand All @@ -114,13 +119,14 @@ fn handle_request(request: Request, auto_cors: bool, cors_referrer: bool, source

impl ServerHandler {
pub fn new(sources: Vec<Pact>, auto_cors: bool, cors_referer: bool, provider_state: Option<Regex>,
provider_state_header_name: Option<String>) -> ServerHandler {
provider_state_header_name: Option<String>, empty_provider_states: bool) -> ServerHandler {
ServerHandler {
sources,
auto_cors,
cors_referer,
provider_state,
provider_state_header_name
provider_state_header_name,
empty_provider_states
}
}

Expand Down Expand Up @@ -165,6 +171,7 @@ impl Service<HyperRequest<Body>> for ServerHandler {
let sources = self.sources.clone();
let provider_state = self.provider_state.clone();
let provider_state_header_name = self.provider_state_header_name.clone();
let empty_provider_states = self.empty_provider_states;

Box::pin(async move {
let (parts, body) = req.into_parts();
Expand Down Expand Up @@ -193,7 +200,8 @@ impl Service<HyperRequest<Body>> for ServerHandler {
}
};
let request = pact_support::hyper_request_to_pact_request(parts, body);
let response = handle_request(request, auto_cors, cors_referrer, sources, provider_state);
let response = handle_request(request, auto_cors, cors_referrer, sources, provider_state,
empty_provider_states);
pact_support::pact_response_to_hyper_response(&response)
})
}
Expand All @@ -220,7 +228,7 @@ mod test {

let request1 = Request::default();

expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None)).to(be_ok().value(interaction1.response));
expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None, false)).to(be_ok().value(interaction1.response));
}

#[test]
Expand All @@ -235,7 +243,7 @@ mod test {

let request1 = Request { method: s!("POST"), .. Request::default() };

expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None)).to(be_err());
expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None, false)).to(be_err());
}

#[test]
Expand All @@ -249,7 +257,7 @@ mod test {

let request1 = Request { path: s!("/two"), .. Request::default() };

expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None)).to(be_err());
expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None, false)).to(be_err());
}

#[test]
Expand All @@ -267,7 +275,7 @@ mod test {
query: Some(hashmap!{ s!("A") => vec![ s!("C") ] }),
.. Request::default() };

expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None)).to(be_err());
expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None, false)).to(be_err());
}

#[test]
Expand Down Expand Up @@ -303,10 +311,10 @@ mod test {
let request4 = Request { method: s!("PUT"), headers: Some(hashmap!{ s!("Content-Type") => vec![s!("application/json")] }),
.. Request::default() };

expect!(super::find_matching_request(&request1, false, false, &vec![pact1.clone(), pact2.clone()], None)).to(be_ok());
expect!(super::find_matching_request(&request2, false, false, &vec![pact1.clone(), pact2.clone()], None)).to(be_err());
expect!(super::find_matching_request(&request3, false, false, &vec![pact1.clone(), pact2.clone()], None)).to(be_ok());
expect!(super::find_matching_request(&request4, false, false, &vec![pact1.clone(), pact2.clone()], None)).to(be_ok());
expect!(super::find_matching_request(&request1, false, false, &vec![pact1.clone(), pact2.clone()], None, false)).to(be_ok());
expect!(super::find_matching_request(&request2, false, false, &vec![pact1.clone(), pact2.clone()], None, false)).to(be_err());
expect!(super::find_matching_request(&request3, false, false, &vec![pact1.clone(), pact2.clone()], None, false)).to(be_ok());
expect!(super::find_matching_request(&request4, false, false, &vec![pact1.clone(), pact2.clone()], None, false)).to(be_ok());
}

#[test]
Expand All @@ -330,7 +338,7 @@ mod test {
body: OptionalBody::Present("{\"a\": 1, \"b\": 4, \"c\": 6}".as_bytes().into()),
.. Request::default() };

expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None)).to(be_ok().value(interaction2.response));
expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2], None, false)).to(be_ok().value(interaction2.response));
}

#[test]
Expand All @@ -342,8 +350,8 @@ mod test {
method: s!("OPTIONS"),
.. Request::default() };

expect!(super::find_matching_request(&request1, true, false, &vec![pact1.clone()], None)).to(be_ok());
expect!(super::find_matching_request(&request1, false, false, &vec![pact1.clone()], None)).to(be_err());
expect!(super::find_matching_request(&request1, true, false, &vec![pact1.clone()], None, false)).to(be_ok());
expect!(super::find_matching_request(&request1, false, false, &vec![pact1.clone()], None, false)).to(be_err());
}

#[test]
Expand Down Expand Up @@ -380,7 +388,7 @@ mod test {
query: Some(hashmap!{ s!("page") => vec![ s!("3") ] }),
.. Request::default() };

expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2.clone()], None)).to(be_ok());
expect!(super::find_matching_request(&request1, false, false, &vec![pact1, pact2.clone()], None, false)).to(be_ok());
}

#[test]
Expand All @@ -407,17 +415,52 @@ mod test {
request: Request::default(),
response: Response { status: 203, .. Response::default() },
.. Interaction::default() };
let interaction4 = Interaction {
response: Response { status: 204, .. Response::default() },
.. Interaction::default() };

let pact = Pact { interactions: vec![ interaction1, interaction2, interaction3 ],
let pact = Pact { interactions: vec![ interaction1, interaction2, interaction3, interaction4 ],
.. Pact::default() };

let request = Request::default();

expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state one").unwrap()))).to(be_ok().value(response1.clone()));
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state two").unwrap()))).to(be_ok().value(response2.clone()));
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state three").unwrap()))).to(be_ok().value(response3.clone()));
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state four").unwrap()))).to(be_err());
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state .*").unwrap()))).to(be_ok().value(response1.clone()));
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state one").unwrap()), false)).to(be_ok().value(response1.clone()));
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state two").unwrap()), false)).to(be_ok().value(response2.clone()));
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state three").unwrap()), false)).to(be_ok().value(response3.clone()));
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state four").unwrap()), false)).to(be_err());
expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("state .*").unwrap()), false)).to(be_ok().value(response1.clone()));
}

#[test]
fn match_request_filters_interactions_if_provider_state_filter_is_provided_and_empty_values_included() {
let interaction1 = Interaction {
provider_states: vec![ ProviderState::default(&"state one".into()) ],
request: Request::default(),
response: Response { status: 201, .. Response::default() },
.. Interaction::default() };

let response2 = Response { status: 202, .. Response::default() };
let interaction2 = Interaction {
provider_states: vec![ ProviderState::default(&"".into()) ],
request: Request::default(),
response: Response { status: 202, .. Response::default() },
.. Interaction::default() };

let response3 = Response { status: 203, .. Response::default() };
let interaction3 = Interaction {
request: Request::default(),
response: Response { status: 203, .. Response::default() },
.. Interaction::default() };

let pact = Pact { interactions: vec![ interaction1.clone(), interaction2.clone(), interaction3.clone() ],
.. Pact::default() };

let request = Request::default();

expect!(super::find_matching_request(&request, false, false, &vec![pact.clone()], Some(Regex::new("any state").unwrap()), true)).to(be_ok().value(response2.clone()));

let pact2 = Pact { interactions: vec![ interaction1.clone(), interaction3.clone() ], .. Pact::default() };
expect!(super::find_matching_request(&request, false, false, &vec![pact2.clone()], Some(Regex::new("any state").unwrap()), true)).to(be_ok().value(response3.clone()));
}

#[test]
Expand All @@ -430,7 +473,7 @@ mod test {

let request = Request { headers: Some(hashmap!{ s!("TEST-X") => vec![s!("X, Y")] }), .. Request::default() };

let result = super::find_matching_request(&request, false, false, &vec![pact], None);
let result = super::find_matching_request(&request, false, false, &vec![pact], None, false);
expect!(result).to(be_ok().value(interaction.response));
}
}

0 comments on commit 729674c

Please sign in to comment.