use crate::server::handlers::{HttpMockRequest, HttpMockResponse, HttpMockState};
use crate::server::util::http::NON_BODY_METHODS;
use crate::server::util::std::{EqNoneAsEmpty, TreeMapOptExtension};
use serde::{Deserialize, Serialize};
pub fn list_all(state: &HttpMockState) -> Result<Vec<SetMockRequest>, String> {
{
let mocks = state.mocks.read().unwrap();
return Result::Ok(mocks.clone());
}
}
pub fn add_new_mock(state: &HttpMockState, req: SetMockRequest) -> Result<(), String> {
let result = validate_mock_request(&req);
if let Err(error_msg) = result {
let error_msg = format!("validation error: {}", error_msg);
return Err(error_msg);
}
{
let mut mocks = state.mocks.write().unwrap();
mocks.push(req);
log::debug!("Number of routes = {}", mocks.len());
}
return Result::Ok(());
}
#[derive(Serialize, Deserialize, TypedBuilder, Clone, Debug)]
pub struct SetMockRequest {
pub request: HttpMockRequest,
pub response: HttpMockResponse,
}
pub fn clear_mocks(state: &HttpMockState) -> Result<(), &'static str> {
{
let mut mocks = state.mocks.write().unwrap();
mocks.clear();
}
return Result::Ok(());
}
pub fn find_mock(
state: &HttpMockState,
req: HttpMockRequest,
) -> Result<Option<HttpMockResponse>, &'static str> {
{
let mocks = state.mocks.read().unwrap();
let result = mocks.iter().find(|&m| request_matches(&req, &m.request));
if let Some(found) = result {
return Ok(Some(found.response.clone()));
}
}
return Result::Ok(None);
}
fn request_matches(req: &HttpMockRequest, mock: &HttpMockRequest) -> bool {
if !&mock.path.eq(&req.path) {
return false;
}
if !&mock.method.eq(&req.method) {
return false;
}
if !req.headers.contains_opt(&mock.headers) {
return false;
}
if !&mock.body.eq_none_as_default(&req.body) {
return false;
}
true
}
fn validate_mock_request(req: &SetMockRequest) -> Result<(), String> {
if req.request.path.is_none() || req.request.path.as_ref().unwrap().trim().is_empty() {
return Err(String::from("You need to provide a path"));
}
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(())
}
#[cfg(test)]
mod test {
use crate::server::handlers::mocks::{request_matches, validate_mock_request, SetMockRequest};
use crate::server::handlers::{HttpMockRequest, HttpMockResponse};
use std::collections::BTreeMap;
#[test]
fn list() {}
#[test]
fn delete_all() {}
#[test]
fn delete_one() {}
#[test]
fn count_calls() {}
#[test]
fn request_matches_path_match() {
let req1: HttpMockRequest = HttpMockRequest::builder()
.path(Some("/test-path".to_string()))
.build();
let req2: HttpMockRequest = HttpMockRequest::builder()
.path(Some("/test-path".to_string()))
.build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(true, does_match_1);
assert_eq!(true, does_match_2);
}
#[test]
fn request_matches_path_no_match() {
let req1: HttpMockRequest = HttpMockRequest::builder()
.path(Some("/test-path".to_string()))
.build();
let req2: HttpMockRequest = HttpMockRequest::builder()
.path(Some("/another-path".to_string()))
.build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(false, does_match_1);
assert_eq!(false, does_match_2);
}
#[test]
fn request_matches_path_no_match_empty() {
let req1: HttpMockRequest = HttpMockRequest::builder().build();
let req2: HttpMockRequest = HttpMockRequest::builder()
.path(Some("/another-path".to_string()))
.build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(false, does_match_1);
assert_eq!(false, does_match_2);
}
#[test]
fn request_matches_method_match() {
let req1: HttpMockRequest = HttpMockRequest::builder()
.method(Some("GET".to_string()))
.build();
let req2: HttpMockRequest = HttpMockRequest::builder()
.method(Some("GET".to_string()))
.build();
let _does_match = request_matches(&req1, &req2);
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(true, does_match_1);
assert_eq!(true, does_match_2);
}
#[test]
fn request_matches_method_no_match() {
let req1: HttpMockRequest = HttpMockRequest::builder()
.method(Some("GET".to_string()))
.build();
let req2: HttpMockRequest = HttpMockRequest::builder()
.method(Some("POST".to_string()))
.build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(false, does_match_1);
assert_eq!(false, does_match_2);
}
#[test]
fn request_matches_method_no_match_empty() {
let req1: HttpMockRequest = HttpMockRequest::builder()
.method(Some("GET".to_string()))
.build();
let req2: HttpMockRequest = HttpMockRequest::builder().build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(false, does_match_1);
assert_eq!(false, does_match_2);
}
#[test]
fn request_matches_body_match() {
let req1: HttpMockRequest = HttpMockRequest::builder()
.body(Some("test".to_string()))
.build();
let req2: HttpMockRequest = HttpMockRequest::builder()
.body(Some("test".to_string()))
.build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(true, does_match_1);
assert_eq!(true, does_match_2);
}
#[test]
fn request_matches_body_no_match() {
let req1: HttpMockRequest = HttpMockRequest::builder()
.body(Some("some text".to_string()))
.build();
let req2: HttpMockRequest = HttpMockRequest::builder()
.body(Some("some other text".to_string()))
.build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(false, does_match_1);
assert_eq!(false, does_match_2);
}
#[test]
fn request_matches_body_no_match_empty() {
let req1: HttpMockRequest = HttpMockRequest::builder()
.body(Some("text".to_string()))
.build();
let req2: HttpMockRequest = HttpMockRequest::builder().build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(false, does_match_1);
assert_eq!(false, does_match_2);
}
#[test]
fn request_matches_body_match_empty() {
let req1: HttpMockRequest = HttpMockRequest::builder().build();
let req2: HttpMockRequest = HttpMockRequest::builder().build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(true, does_match_1);
assert_eq!(true, does_match_2);
}
#[test]
fn request_matches_headers_exact_match() {
let mut h1 = BTreeMap::new();
h1.insert("h1".to_string(), "v1".to_string());
h1.insert("h2".to_string(), "v2".to_string());
let mut h2 = BTreeMap::new();
h2.insert("h1".to_string(), "v1".to_string());
h2.insert("h2".to_string(), "v2".to_string());
let req1: HttpMockRequest = HttpMockRequest::builder().headers(Some(h1)).build();
let req2: HttpMockRequest = HttpMockRequest::builder().headers(Some(h2)).build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(true, does_match_1);
assert_eq!(true, does_match_2);
}
#[test]
fn request_matches_headers_no_match() {
let mut h1 = BTreeMap::new();
h1.insert("h1".to_string(), "v1".to_string());
let mut h2 = BTreeMap::new();
h2.insert("h1".to_string(), "v1".to_string());
h2.insert("h2".to_string(), "v2".to_string());
let req1: HttpMockRequest = HttpMockRequest::builder().headers(Some(h1)).build();
let req2: HttpMockRequest = HttpMockRequest::builder().headers(Some(h2)).build();
let does_match = request_matches(&req1, &req2);
assert_eq!(false, does_match); }
#[test]
fn request_matches_headers_match_superset() {
let mut h1 = BTreeMap::new();
h1.insert("h1".to_string(), "v1".to_string());
h1.insert("h2".to_string(), "v2".to_string());
let mut h2 = BTreeMap::new();
h2.insert("h1".to_string(), "v1".to_string());
let req1: HttpMockRequest = HttpMockRequest::builder().headers(Some(h1)).build();
let req2: HttpMockRequest = HttpMockRequest::builder().headers(Some(h2)).build();
let does_match = request_matches(&req1, &req2);
assert_eq!(true, does_match); }
#[test]
fn request_matches_headers_no_match_empty() {
let mut req_headers = BTreeMap::new();
req_headers.insert("req_headers".to_string(), "v1".to_string());
req_headers.insert("h2".to_string(), "v2".to_string());
let req: HttpMockRequest = HttpMockRequest::builder()
.headers(Some(req_headers))
.build();
let mock: HttpMockRequest = HttpMockRequest::builder().headers(None).build();
let does_match_1 = request_matches(&req, &mock);
assert_eq!(true, does_match_1); }
#[test]
fn request_matches_headers_match_empty() {
let req1: HttpMockRequest = HttpMockRequest::builder().headers(None).build();
let req2: HttpMockRequest = HttpMockRequest::builder().headers(None).build();
let does_match_1 = request_matches(&req1, &req2);
let does_match_2 = request_matches(&req2, &req1);
assert_eq!(true, does_match_1);
assert_eq!(true, does_match_2);
}
#[test]
fn validate_mock_request_no_body_method() {
let req: HttpMockRequest = HttpMockRequest::builder()
.path(Some("/test".to_string()))
.method(Some("GET".to_string()))
.body(Some("test".to_string()))
.build();
let res: HttpMockResponse = HttpMockResponse::builder().status(418 as u16).build();
let smr: SetMockRequest = SetMockRequest::builder().request(req).response(res).build();
let result = validate_mock_request(&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_request_no_path() {
let req: HttpMockRequest = HttpMockRequest::builder().build();
let res: HttpMockResponse = HttpMockResponse::builder().status(418 as u16).build();
let smr: SetMockRequest = SetMockRequest::builder().request(req).response(res).build();
let result = validate_mock_request(&smr);
assert_eq!(true, result.is_err());
assert_eq!(true, result.unwrap_err().eq("You need to provide a path"));
}
}