use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::str::FromStr;
use std::sync::Arc;
use basic_cookies::Cookie;
use serde_json::Value;
use crate::data::{
ActiveMock, ClosestMatch, HttpMockRequest, MockDefinition, MockServerHttpResponse,
RequestRequirements,
};
use crate::server::matchers::Matcher;
use crate::server::util::{StringTreeMapExtension, TreeMapExtension};
use crate::server::{Mismatch, MockServerState};
const NON_BODY_METHODS: &[&str] = &["GET", "HEAD"];
pub(crate) fn add_new_mock(
state: &MockServerState,
mock_def: MockDefinition,
is_static: bool,
) -> Result<usize, String> {
let result = validate_mock_definition(&mock_def);
if let Err(error_msg) = result {
let error_msg = format!("Validation error: {}", error_msg);
return Err(error_msg);
}
let mock_id = state.create_new_id();
{
log::debug!("Adding new mock with ID={}", mock_id);
let mut mocks = state.mocks.write().unwrap();
mocks.insert(mock_id, ActiveMock::new(mock_id, mock_def, is_static));
}
Result::Ok(mock_id)
}
pub(crate) fn read_one_mock(
state: &MockServerState,
id: usize,
) -> Result<Option<ActiveMock>, String> {
{
let mocks = state.mocks.read().unwrap();
let result = mocks.get(&id);
match result {
Some(found) => Ok(Some(found.clone())),
None => Ok(None),
}
}
}
pub(crate) fn delete_one_mock(state: &MockServerState, id: usize) -> Result<bool, String> {
let result;
{
let mut mocks = state.mocks.write().unwrap();
if let Some(m) = mocks.get(&id) {
if m.is_static {
return Err(format!("Cannot delete static mock with ID {}", id));
}
}
result = mocks.remove(&id);
}
log::debug!("Deleted mock with id={}", id);
Result::Ok(result.is_some())
}
pub(crate) fn delete_all_mocks(state: &MockServerState) {
let mut mocks = state.mocks.write().unwrap();
let ids: Vec<usize> = mocks
.iter()
.filter(|(k, v)| !v.is_static)
.map(|(k, v)| *k)
.collect();
ids.iter().for_each(|k| {
mocks.remove(k);
});
log::trace!("Deleted all mocks");
}
pub(crate) fn delete_history(state: &MockServerState) {
let mut mocks = state.history.write().unwrap();
mocks.clear();
log::trace!("Deleted request history");
}
pub(crate) fn find_mock(
state: &MockServerState,
req: HttpMockRequest,
) -> Result<Option<MockServerHttpResponse>, String> {
let req = Arc::new(req);
{
let mut history = state.history.write().unwrap();
history.push(req.clone());
}
let found_mock_id: Option<usize>;
{
let mocks = state.mocks.read().unwrap();
let result = mocks
.values()
.find(|&mock| request_matches(&state, req.clone(), &mock.definition.request));
found_mock_id = match result {
Some(mock) => Some(mock.id),
None => None,
};
}
if let Some(found_id) = found_mock_id {
log::debug!(
"Matched mock with id={} to the following request: {:#?}",
found_id,
req
);
let mut mocks = state.mocks.write().unwrap();
let mock = mocks.get_mut(&found_id).unwrap();
mock.call_counter += 1;
return Ok(Some(mock.definition.response.clone()));
}
log::debug!(
"Could not match any mock to the following request: {:#?}",
req
);
Result::Ok(None)
}
fn request_matches(
state: &MockServerState,
req: Arc<HttpMockRequest>,
mock: &RequestRequirements,
) -> bool {
log::trace!("Matching incoming HTTP request");
state
.matchers
.iter()
.enumerate()
.all(|(i, x)| x.matches(&req, mock))
}
pub(crate) fn verify(
state: &MockServerState,
mock_rr: &RequestRequirements,
) -> Result<Option<ClosestMatch>, String> {
let mut history = state.history.write().unwrap();
let non_matching_requests: Vec<&Arc<HttpMockRequest>> = history
.iter()
.filter(|a| !request_matches(state, (*a).clone(), mock_rr))
.collect();
let request_distances = get_distances(&non_matching_requests, &state.matchers, mock_rr);
let best_matches = get_min_distance_requests(&request_distances);
let closes_match_request_idx = match best_matches.get(0) {
None => return Ok(None),
Some(idx) => *idx,
};
let req = non_matching_requests.get(closes_match_request_idx).unwrap();
let mismatches = get_request_mismatches(req, &mock_rr, &state.matchers);
Ok(Some(ClosestMatch {
request: HttpMockRequest::clone(&req),
request_index: closes_match_request_idx,
mismatches,
}))
}
fn validate_mock_definition(req: &MockDefinition) -> Result<(), String> {
if let Some(_body) = &req.request.body {
if let Some(method) = &req.request.method {
if NON_BODY_METHODS.contains(&method.as_str()) {
return Err(String::from(
"A body cannot be sent along with the specified method",
));
}
}
}
Ok(())
}
fn get_distances(
history: &Vec<&Arc<HttpMockRequest>>,
matchers: &Vec<Box<dyn Matcher + Sync + Send>>,
mock_rr: &RequestRequirements,
) -> BTreeMap<usize, usize> {
history
.iter()
.enumerate()
.map(|(idx, req)| (idx, get_request_distance(req, mock_rr, matchers)))
.collect()
}
fn get_request_mismatches(
req: &Arc<HttpMockRequest>,
mock_rr: &RequestRequirements,
matchers: &Vec<Box<dyn Matcher + Sync + Send>>,
) -> Vec<Mismatch> {
matchers
.iter()
.map(|mat| mat.mismatches(req, mock_rr))
.flatten()
.into_iter()
.collect()
}
fn get_request_distance(
req: &Arc<HttpMockRequest>,
mock_rr: &RequestRequirements,
matchers: &Vec<Box<dyn Matcher + Sync + Send>>,
) -> usize {
matchers
.iter()
.map(|matcher| matcher.distance(req, mock_rr))
.sum()
}
fn get_min_distance_requests(request_distances: &BTreeMap<usize, usize>) -> Vec<usize> {
let min_elem = request_distances
.iter()
.min_by(|(idx1, d1), (idx2, d2)| (**d1).cmp(d2));
let max = match min_elem {
None => return Vec::new(),
Some((_, n)) => *n,
};
request_distances
.into_iter()
.filter(|(idx, distance)| **distance == max)
.map(|(idx, _)| *idx)
.collect()
}
#[cfg(test)]
mod test {
use std::collections::BTreeMap;
use std::rc::Rc;
use std::sync::Arc;
use regex::Regex;
use crate::data::{
HttpMockRequest, MockDefinition, MockServerHttpResponse, Pattern, RequestRequirements,
};
use crate::server::web::handlers::{
add_new_mock, read_one_mock, request_matches, validate_mock_definition, verify,
};
use crate::server::MockServerState;
use crate::Method;
#[test]
fn header_names_case_insensitive() {}
#[test]
fn parsing_query_params_test() {}
#[test]
fn parsing_query_contains_test() {}
#[test]
fn header_exists_test() {}
#[test]
fn path_contains_test() {}
#[test]
fn path_pattern_test() {}
#[test]
fn body_contains_test() {
let request1 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string())
.with_body("test".as_bytes().to_vec());
let request2 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string())
.with_body("test".as_bytes().to_vec());
let requirements1 = RequestRequirements::new().with_body_contains(vec!["xxx".to_string()]);
let requirements2 = RequestRequirements::new().with_body_contains(vec!["es".to_string()]);
let does_match1 =
request_matches(&MockServerState::new(), Arc::new(request1), &requirements1);
let does_match2 =
request_matches(&MockServerState::new(), Arc::new(request2), &requirements2);
assert_eq!(false, does_match1);
assert_eq!(true, does_match2);
}
#[test]
fn body_matches_query_params_exact_test() {
let mut params1 = Vec::new();
params1.push(("k".to_string(), "v".to_string()));
let mut params2 = Vec::new();
params2.push(("h".to_string(), "o".to_string()));
let request1 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string())
.with_query_params(params1.clone());
let request2 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string())
.with_query_params(params1.clone());
let requirements1 = RequestRequirements::new().with_query_param(params2);
let requirements2 = RequestRequirements::new().with_query_param(params1.clone());
let does_match1 =
request_matches(&MockServerState::new(), Arc::new(request1), &requirements1);
let does_match2 =
request_matches(&MockServerState::new(), Arc::new(request2), &requirements2);
assert_eq!(false, does_match1);
assert_eq!(true, does_match2);
}
#[test]
fn body_contains_includes_json_test() {}
#[test]
fn body_json_exact_match_test() {}
#[test]
fn request_matches_path_match() {
let req1 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string());
let req2 = RequestRequirements::new().with_path("/test-path".to_string());
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(true, does_match);
}
#[test]
fn request_matches_path_no_match() {
let req1 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string());
let req2 = RequestRequirements::new().with_path("/another-path".to_string());
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(false, does_match);
}
#[test]
fn request_matches_method_match() {
let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string());
let req2 = RequestRequirements::new().with_method("GET".to_string());
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(true, does_match);
}
#[test]
fn request_matches_method_no_match() {
let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string());
let req2 = RequestRequirements::new().with_method("POST".to_string());
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(false, does_match);
}
#[test]
fn request_matches_body_match() {
let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string())
.with_body("test".as_bytes().to_vec());
let req2 = RequestRequirements::new().with_body("test".to_string());
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(true, does_match);
}
#[test]
fn request_matches_body_no_match() {
let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string())
.with_body("some text".as_bytes().to_vec());
let req2 = RequestRequirements::new().with_body("some other text".to_string());
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(false, does_match);
}
#[test]
fn request_matches_headers_exact_match() {
let mut h1 = Vec::new();
h1.push(("h1".to_string(), "v1".to_string()));
h1.push(("h2".to_string(), "v2".to_string()));
let mut h2 = Vec::new();
h2.push(("h1".to_string(), "v1".to_string()));
h2.push(("h2".to_string(), "v2".to_string()));
let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()).with_headers(h1);
let req2 = RequestRequirements::new().with_headers(h2);
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(true, does_match);
}
#[test]
fn request_matches_query_param() {
let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string())
.with_body("test".as_bytes().to_vec());
let req2 = RequestRequirements::new().with_body("test".to_string());
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(true, does_match);
}
#[test]
fn request_matches_headers_match_superset() {
let mut h1 = Vec::new();
h1.push(("h1".to_string(), "v1".to_string()));
h1.push(("h2".to_string(), "v2".to_string()));
let mut h2 = Vec::new();
h2.push(("h1".to_string(), "v1".to_string()));
let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()).with_headers(h1);
let req2 = RequestRequirements::new().with_headers(h2);
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(true, does_match); }
#[test]
fn request_matches_headers_no_match_empty() {
let mut req_headers = Vec::new();
req_headers.push(("req_headers".to_string(), "v1".to_string()));
req_headers.push(("h2".to_string(), "v2".to_string()));
let req =
HttpMockRequest::new("GET".to_string(), "/test".to_string()).with_headers(req_headers);
let mock = RequestRequirements::new();
let does_match_1 = request_matches(&MockServerState::new(), Arc::new(req), &mock);
assert_eq!(true, does_match_1); }
#[test]
fn request_matches_headers_match_empty() {
let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string());
let req2 = RequestRequirements::new();
let does_match = request_matches(&MockServerState::new(), Arc::new(req1), &req2);
assert_eq!(true, does_match);
}
#[test]
fn validate_mock_definition_no_body_method() {
let req = RequestRequirements::new()
.with_path("/test".to_string())
.with_method("GET".to_string())
.with_body("test".to_string());
let res = MockServerHttpResponse {
body: None,
delay: None,
status: Some(418),
headers: None,
};
let smr = MockDefinition::new(req, res);
let result = validate_mock_definition(&smr);
assert_eq!(true, result.is_err());
assert_eq!(
true,
result
.unwrap_err()
.eq("A body cannot be sent along with the specified method")
);
}
#[test]
fn validate_mock_definition_no_path() {
let req = RequestRequirements::new();
let res = MockServerHttpResponse {
body: None,
delay: None,
status: Some(418),
headers: None,
};
let smr = MockDefinition::new(req, res);
let result = validate_mock_definition(&smr);
assert_eq!(true, result.is_ok());
}
#[test]
fn add_new_mock_validation_error() {
let state = MockServerState::new();
let mut req = RequestRequirements::new();
req.method = Some("GET".into());
req.body = Some("body".into());
let res = MockServerHttpResponse {
body: None,
delay: None,
status: Some(200),
headers: None,
};
let mock_def = MockDefinition::new(req, res);
let result = add_new_mock(&state, mock_def, false);
assert_eq!(result.is_err(), true);
assert_eq!(result.err().unwrap().contains("Validation error"), true);
}
#[test]
fn read_one_returns_none_test() {
let state = MockServerState::new();
let result = read_one_mock(&state, 6);
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap().is_none(), true);
}
#[test]
fn not_match_path_contains_test() {
let msr = Arc::new(HttpMockRequest::new("GET".into(), "test".into()));
let mut mock1 = RequestRequirements::new();
mock1.path_contains = Some(vec!["x".into()]);
let mut mock2 = RequestRequirements::new();
mock2.path_contains = Some(vec!["es".into()]);
let result1 = request_matches(&MockServerState::new(), msr.clone(), &mock1);
let result2 = request_matches(&MockServerState::new(), msr.clone(), &mock2);
assert_eq!(result1, false);
assert_eq!(result2, true);
}
#[test]
fn not_match_path_matches_test() {
let msr = Arc::new(HttpMockRequest::new("GET".into(), "test".into()));
let mut mock1 = RequestRequirements::new();
mock1.path_matches = Some(vec![Pattern::from_regex(Regex::new(r#"x"#).unwrap())]);
let mut mock2 = RequestRequirements::new();
mock2.path_matches = Some(vec![Pattern::from_regex(Regex::new(r#"test"#).unwrap())]);
let result1 = request_matches(&MockServerState::new(), msr.clone(), &mock1);
let result2 = request_matches(&MockServerState::new(), msr.clone(), &mock2);
assert_eq!(result1, false);
assert_eq!(result2, true);
}
#[test]
fn verify_test() {
let mut mock_server_state = MockServerState::new();
{
let mut mocks = mock_server_state.history.write().unwrap();
mocks.push(Arc::new(HttpMockRequest::new(
String::from("POST"),
String::from("/Brians"),
)));
mocks.push(Arc::new(HttpMockRequest::new(
String::from("GET"),
String::from("/Briann"),
)));
mocks.push(Arc::new(HttpMockRequest::new(
String::from("DELETE"),
String::from("/xxxxxxx/xxxxxx"),
)));
}
let mut rr = RequestRequirements::new();
rr.method = Some("GET".to_string());
rr.path = Some("/Briann".to_string());
let result = verify(&mock_server_state, &rr);
assert_eq!(result.as_ref().is_ok(), true);
assert_eq!(result.as_ref().unwrap().is_some(), true);
assert_eq!(result.as_ref().unwrap().as_ref().unwrap().request_index, 0);
}
}