diff --git a/src/lib.rs b/src/lib.rs index eee8044..4d9d151 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -347,6 +347,27 @@ //! .create(); //!``` //! +//! # The `AllOf` matcher +//! +//! The `Matcher::AllOf` construct takes a vector of matchers as arguments and will be enabled +//! if all of the provided matchers match the request. +//! +//! ## Example +//! +//! ``` +//! use mockito::{mock, Matcher}; +//! +//! // Will match requests to POST / whenever the request body contains both `hello` and `world` +//! let _m = mock("POST", "/") +//! .match_body( +//! Matcher::AllOf(vec![ +//! Matcher::Regex("hello".to_string()), +//! Matcher::Regex("world".to_string()), +//! ]) +//! ) +//! .create(); +//!``` +//! //! # Non-matching calls //! //! Any calls to the Mockito server that are not matched will return *501 Mock Not Found*. @@ -504,8 +525,10 @@ pub enum Matcher { Json(serde_json::Value), /// Matches a specified JSON body from a `String` JsonString(String), - /// At least one must match + /// At least one matcher must match AnyOf(Vec), + /// All matchers must match + AllOf(Vec), /// Matches any path or any header value. Any, /// Checks that a header is not present in the request. @@ -529,6 +552,9 @@ impl Matcher { Matcher::AnyOf(ref matchers) if header_values.is_empty() => { matchers.iter().any(|m| m.matches_values(header_values)) }, + Matcher::AllOf(ref matchers) if header_values.is_empty() => { + matchers.iter().all(|m| m.matches_values(header_values)) + }, _ => !header_values.is_empty() && header_values.iter().all(|val| self.matches_value(val)), } } @@ -551,6 +577,9 @@ impl Matcher { Matcher::AnyOf(ref matchers) => { matchers.iter().any(|m| m.matches_value(other)) }, + Matcher::AllOf(ref matchers) => { + matchers.iter().all(|m| m.matches_value(other)) + }, Matcher::Missing => false, } } @@ -831,6 +860,7 @@ impl fmt::Display for Mock { }, Matcher::Any => formatted.push_str("(any)\r\n"), Matcher::AnyOf(..) => formatted.push_str("(any of)\r\n"), + Matcher::AllOf(..) => formatted.push_str("(all of)\r\n"), Matcher::Missing => formatted.push_str("(missing)\r\n"), } @@ -874,6 +904,11 @@ impl fmt::Display for Mock { formatted.push_str(": "); formatted.push_str("(any of)"); }, + Matcher::AllOf(..) => { + formatted.push_str(key); + formatted.push_str(": "); + formatted.push_str("(all of)"); + }, } formatted.push_str("\r\n"); @@ -890,6 +925,7 @@ impl fmt::Display for Mock { }, Matcher::Missing => formatted.push_str("(missing)\r\n"), Matcher::AnyOf(..) => formatted.push_str("(any of)\r\n"), + Matcher::AllOf(..) => formatted.push_str("(all of)\r\n"), Matcher::Any => {} } diff --git a/tests/lib.rs b/tests/lib.rs index 17a4ea3..1736819 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -459,7 +459,7 @@ fn test_regex_match_header() { } #[test] -fn test_or_match_header() { +fn test_any_of_match_header() { let _m = mock("GET", "/") .match_header("Via", Matcher::AnyOf(vec![ Matcher::Exact("one".into()), @@ -484,7 +484,28 @@ fn test_or_match_header() { } #[test] -fn test_or_miss_match_header() { +fn test_any_of_match_body() { + let _m = mock("GET", "/") + .match_body(Matcher::AnyOf(vec![ + Matcher::Regex("one".to_string()), + Matcher::Regex("two".to_string())])) + .create(); + + let (status_line, _, _) = request_with_body("GET /", "", "one"); + assert!(status_line.starts_with("HTTP/1.1 200 ")); + + let (status_line, _, _) = request_with_body("GET /", "", "two"); + assert!(status_line.starts_with("HTTP/1.1 200 ")); + + let (status_line, _, _) = request_with_body("GET /", "", "one two"); + assert!(status_line.starts_with("HTTP/1.1 200 ")); + + let (status_line, _, _) = request_with_body("GET /", "", "three"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); +} + +#[test] +fn test_any_of_missing_match_header() { let _m = mock("GET", "/") .match_header("Via", Matcher::AnyOf(vec![ Matcher::Exact("one".into()), @@ -511,6 +532,79 @@ fn test_or_miss_match_header() { assert!(status_line.starts_with("HTTP/1.1 501 ")); } +#[test] +fn test_all_of_match_header() { + let _m = mock("GET", "/") + .match_header("Via", Matcher::AllOf(vec![ + Matcher::Regex("one".into()), + Matcher::Regex("two".into())])) + .with_body("{}") + .create(); + + let (status_line, _, _) = request("GET /", "Via: one\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request("GET /", "Via: two\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request("GET /", "Via: one two\r\nVia: one two three\r\n"); + assert!(status_line.starts_with("HTTP/1.1 200 ")); + + let (status_line, _, _) = request("GET /", "Via: one\r\nVia: two\r\nVia: wrong\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request("GET /", "Via: wrong\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); +} + +#[test] +fn test_all_of_match_body() { + let _m = mock("GET", "/") + .match_body(Matcher::AllOf(vec![ + Matcher::Regex("one".to_string()), + Matcher::Regex("two".to_string())])) + .create(); + + let (status_line, _, _) = request_with_body("GET /", "", "one"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request_with_body("GET /", "", "two"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request_with_body("GET /", "", "one two"); + assert!(status_line.starts_with("HTTP/1.1 200 ")); + + let (status_line, _, _) = request_with_body("GET /", "", "three"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); +} + +#[test] +fn test_all_of_missing_match_header() { + let _m = mock("GET", "/") + .match_header("Via", Matcher::AllOf(vec![ + Matcher::Missing])) + .with_body("{}") + .create(); + + let (status_line, _, _) = request("GET /", "Via: one\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request("GET /", "Via: one\r\nVia: one\r\nVia: one\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request("GET /", "NotVia: one\r\n"); + assert!(status_line.starts_with("HTTP/1.1 200 ")); + + let (status_line, _, _) = request("GET /", "Via: wrong\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request("GET /", "Via: wrong\r\nVia: one\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); + + let (status_line, _, _) = request("GET /", "Via: one\r\nVia: wrong\r\n"); + assert!(status_line.starts_with("HTTP/1.1 501 ")); +} + #[test] fn test_large_utf8_body() { let mock_body: String = rand::thread_rng()