diff --git a/src/api/adapter/local.rs b/src/api/adapter/local.rs index b34ae82..c50aa03 100644 --- a/src/api/adapter/local.rs +++ b/src/api/adapter/local.rs @@ -12,8 +12,15 @@ use async_std::prelude::*; use crate::api::adapter::MockServerAdapter; -use crate::common::data::{ActiveMock, ClosestMatch, MockDefinition, MockRef, ProxyMatcherRef, RecordingMatcherRef, RequestRequirements}; -use crate::server::web::handlers::{add_new_mock, add_proxy_matcher, add_recording_matcher, delete_all_mocks, delete_all_proxy_matchers, delete_all_recording_matchers, delete_history, delete_one_mock, read_one_mock, reset, verify}; +use crate::common::data::{ + ActiveMock, ClosestMatch, MockDefinition, MockRef, ProxyMatcherRef, RecordingMatcherRef, + RequestRequirements, +}; +use crate::server::web::handlers::{ + add_new_mock, add_proxy_matcher, add_recording_matcher, delete_all_mocks, + delete_all_proxy_matchers, delete_all_recording_matchers, delete_history, delete_one_mock, + read_one_mock, reset, verify, +}; use crate::server::MockServerState; pub struct LocalMockServerAdapter { @@ -115,16 +122,22 @@ impl MockServerAdapter for LocalMockServerAdapter { Ok(()) } - async fn create_proxy_matcher(&self, req: RequestRequirements) -> Result { + async fn create_proxy_matcher( + &self, + req: RequestRequirements, + ) -> Result { add_proxy_matcher(&self.local_state, req) } async fn delete_all_proxy_matchers(&self) -> Result<(), String> { - delete_all_proxy_matchers(&self.local_state); + delete_all_proxy_matchers(&self.local_state); Ok(()) } - async fn create_record_matcher(&self, req: RequestRequirements) -> Result { + async fn create_record_matcher( + &self, + req: RequestRequirements, + ) -> Result { add_recording_matcher(&self.local_state, req) } diff --git a/src/api/adapter/mod.rs b/src/api/adapter/mod.rs index c560475..d663ca0 100644 --- a/src/api/adapter/mod.rs +++ b/src/api/adapter/mod.rs @@ -7,7 +7,10 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use crate::common::data::{ActiveMock, ClosestMatch, MockDefinition, MockRef, ProxyMatcherRef, RecordingMatcherRef, RequestRequirements}; +use crate::common::data::{ + ActiveMock, ClosestMatch, MockDefinition, MockRef, ProxyMatcherRef, RecordingMatcherRef, + RequestRequirements, +}; use crate::server::web::handlers::{ add_new_mock, delete_all_mocks, delete_history, delete_one_mock, read_one_mock, verify, }; @@ -77,9 +80,15 @@ pub trait MockServerAdapter { async fn verify(&self, rr: &RequestRequirements) -> Result, String>; async fn delete_history(&self) -> Result<(), String>; async fn ping(&self) -> Result<(), String>; - async fn create_proxy_matcher(&self, req: RequestRequirements) -> Result<(ProxyMatcherRef), String>; + async fn create_proxy_matcher( + &self, + req: RequestRequirements, + ) -> Result<(ProxyMatcherRef), String>; async fn delete_all_proxy_matchers(&self) -> Result<(), String>; - async fn create_record_matcher(&self, req: RequestRequirements) -> Result<(RecordingMatcherRef), String>; + async fn create_record_matcher( + &self, + req: RequestRequirements, + ) -> Result<(RecordingMatcherRef), String>; async fn delete_all_record_matchers(&self) -> Result<(), String>; async fn reset(&self) -> Result<(), String>; } diff --git a/src/api/adapter/standalone.rs b/src/api/adapter/standalone.rs index 9c4ee18..6e14974 100644 --- a/src/api/adapter/standalone.rs +++ b/src/api/adapter/standalone.rs @@ -250,7 +250,6 @@ impl MockServerAdapter for RemoteMockServerAdapter { async fn ping(&self) -> Result<(), String> { http_ping(&self.addr, self.http_client.borrow()).await } - } async fn http_ping( diff --git a/src/api/server.rs b/src/api/server.rs index 1ca747d..64ef9ed 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,3 +1,4 @@ +use crate::api::mock::{ProxyMatcher, RecordingMatcher}; use crate::api::spec::{Then, When}; #[cfg(feature = "remote")] use crate::api::RemoteMockServerAdapter; @@ -14,7 +15,6 @@ use std::rc::Rc; use std::sync::Arc; use std::thread; use tokio::task::LocalSet; -use crate::api::mock::{ProxyMatcher, RecordingMatcher}; /// A mock server that is able to receive and respond to HTTP requests. pub struct MockServer { @@ -328,15 +328,22 @@ impl MockServer { } } - - pub fn proxy(&self, host: F) -> ProxyMatcher where F: FnOnce(When) { + pub fn proxy(&self, host: F) -> ProxyMatcher + where + F: FnOnce(When), + { self.proxy_async(host).join() } - pub async fn proxy_async(&self, spec_fn: F) -> ProxyMatcher where F: FnOnce(When) { + pub async fn proxy_async(&self, spec_fn: F) -> ProxyMatcher + where + F: FnOnce(When), + { let mut req = Rc::new(Cell::new(RequestRequirements::new())); - spec_fn(When { expectations: req.clone() }); + spec_fn(When { + expectations: req.clone(), + }); let response = self .server_adapter @@ -352,15 +359,22 @@ impl MockServer { } } - - pub fn record(&self, host: F) -> RecordingMatcher where F: FnOnce(When) { + pub fn record(&self, host: F) -> RecordingMatcher + where + F: FnOnce(When), + { self.record_async(host).join() } - pub async fn record_async(&self, spec_fn: F) -> RecordingMatcher where F: FnOnce(When){ + pub async fn record_async(&self, spec_fn: F) -> RecordingMatcher + where + F: FnOnce(When), + { let mut req = Rc::new(Cell::new(RequestRequirements::new())); - spec_fn(When { expectations: req.clone() }); + spec_fn(When { + expectations: req.clone(), + }); let response = self .server_adapter @@ -375,7 +389,6 @@ impl MockServer { server: self, } } - } impl Drop for MockServer { diff --git a/src/common/data.rs b/src/common/data.rs index 76b3e6f..323c677 100644 --- a/src/common/data.rs +++ b/src/common/data.rs @@ -371,7 +371,6 @@ impl RecordingMatcherRef { } } - #[derive(Serialize, Deserialize, Clone)] pub struct ActiveProxyMatcher { pub id: usize, diff --git a/src/server/mod.rs b/src/server/mod.rs index e597ab0..2ed34d0 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -23,7 +23,9 @@ use regex::Regex; use matchers::generic::SingleValueMatcher; use matchers::targets::{JSONBodyTarget, StringBodyTarget}; -use crate::common::data::{ActiveMock, ActiveProxyMatcher, HttpMockRequest, RequestRequirements, Tokenizer}; +use crate::common::data::{ + ActiveMock, ActiveProxyMatcher, HttpMockRequest, RequestRequirements, Tokenizer, +}; use crate::server::matchers::comparators::{ AnyValueComparator, FunctionMatchesRequestComparator, JSONContainsMatchComparator, JSONExactMatchComparator, StringContainsMatchComparator, StringExactMatchComparator, @@ -48,18 +50,19 @@ use crate::server::web::routes; use futures_util::task::Spawn; use std::future::Future; +use crate::server::proxy::try_proxy_request; use futures_util::FutureExt; +use http_body_util::combinators::BoxBody; use http_body_util::{BodyExt, Full}; -use std::time::Instant; use serde_json::ser::State::Empty; +use std::time::Instant; use tokio::net::TcpListener; -use crate::server::proxy::try_proxy_request; mod matchers; +mod proxy; mod util; pub(crate) mod web; -mod proxy; /// The shared state accessible to all handlers pub struct MockServerState { @@ -170,7 +173,7 @@ impl MockServerState { }), // Cookie exact #[cfg(feature = "cookies")] - Box::new(MultiValueMatcher { + Box::new(MultiValueMatcher { entity_name: "cookie", key_comparator: Box::new(StringExactMatchComparator::new(true)), value_comparator: Box::new(StringExactMatchComparator::new(true)), @@ -184,7 +187,7 @@ impl MockServerState { }), // Cookie exists #[cfg(feature = "cookies")] - Box::new(MultiValueMatcher { + Box::new(MultiValueMatcher { entity_name: "cookie", key_comparator: Box::new(StringExactMatchComparator::new(true)), value_comparator: Box::new(AnyValueComparator::new()), @@ -403,8 +406,8 @@ async fn access_log_middleware( print_access_log: bool, next: fn(req: HyperRequest, state: Arc) -> T, ) -> HyperResult>> - where - T: Future>>>, +where + T: Future>>>, { let time_request_received = Instant::now(); @@ -430,15 +433,18 @@ async fn access_log_middleware( return result; } -async fn handle_server_request(req: HyperRequest, state: Arc) -> HyperResult>> { +async fn handle_server_request( + req: HyperRequest, + state: Arc, +) -> HyperResult>> { let _ = try_proxy_request(req).await; HyperResult::Ok(HyperResponse::new(Full::new(Bytes::new()))) - /* - if let Ok(Some(proxy_result)) = try_proxy_request(req).await { + /* + if let Ok(Some(proxy_result)) = try_proxy_request(req).await { return HyperResult::Ok(proxy_result) - } + } let res = handle_mock_request(req, state).await; @@ -453,106 +459,107 @@ async fn handle_server_request(req: HyperRequest, state: */ } +fn is_recording_request(req: &HyperRequest) -> bool { + false +} - fn is_recording_request(req: &HyperRequest) -> bool { - false - } +fn record_request( + req: &HyperRequest, + res: &HyperResult>>, +) -> Result<(), String> { + Ok(()) +} - fn record_request(req: &HyperRequest, res: &HyperResult>>) -> Result<(), String> { - Ok(()) +fn handle_proxy_request( + req: HyperRequest, + state: Arc, +) -> HyperResult>> { + return Ok(HyperResponse::new(Full::::new(Bytes::new()))); +} + +async fn handle_mock_request( + req: HyperRequest, + state: Arc, +) -> HyperResult>> { + let request_header = ServerRequestHeader::from(&req); + if let Err(e) = request_header { + return Ok(error_response(format!("Cannot parse request: {}", e))); } - fn handle_proxy_request( - req: HyperRequest, - state: Arc, - ) -> HyperResult>> { - return Ok(HyperResponse::new(Full::::new(Bytes::new()))) + let body_parts = BodyExt::collect(req).await; + if let Err(e) = body_parts { + return Ok(error_response(format!( + "Cannot read request body chunks: {}", + e + ))); } - async fn handle_mock_request( - req: HyperRequest, - state: Arc, - ) -> HyperResult>> { - let request_header = ServerRequestHeader::from(&req); - if let Err(e) = request_header { - return Ok(error_response(format!("Cannot parse request: {}", e))); - } + let full_body_bytes = body_parts.unwrap().to_bytes(); - let body_parts = BodyExt::collect(req).await; - if let Err(e) = body_parts { - return Ok(error_response(format!( - "Cannot read request body chunks: {}", - e - ))); - } + let routing_result = route_request( + state.borrow(), + &request_header.unwrap(), + full_body_bytes.to_vec(), + ) + .await; + if let Err(e) = routing_result { + return Ok(error_response(format!("Request handler error: {}", e))); + } - let full_body_bytes = body_parts.unwrap().to_bytes(); + let response = map_response(routing_result.unwrap()); + if let Err(e) = response { + return Ok(error_response(format!("Cannot build response: {}", e))); + } - let routing_result = route_request( - state.borrow(), - &request_header.unwrap(), - full_body_bytes.to_vec(), - ) - .await; - if let Err(e) = routing_result { - return Ok(error_response(format!("Request handler error: {}", e))); - } + Ok(response.unwrap()) +} - let response = map_response(routing_result.unwrap()); - if let Err(e) = response { - return Ok(error_response(format!("Cannot build response: {}", e))); - } +async fn proxy_request( + req: HyperRequest, + state: Arc, +) -> HyperResult>> { + return Ok(HyperResponse::new(Full::::new(Bytes::new()))); +} - Ok(response.unwrap()) - } - - - async fn proxy_request( - req: HyperRequest, - state: Arc, - ) -> HyperResult>> { - return Ok(HyperResponse::new(Full::::new(Bytes::new()))) - } - - /// Starts a new instance of an HTTP mock server. You should never need to use this function - /// directly. Use it if you absolutely need to manage the low-level details of how the mock - /// server operates. - pub(crate) async fn start_server( - port: u16, - expose: bool, - state: &Arc, - socket_addr_sender: Option>, - print_access_log: bool, - shutdown: F, - ) -> Result<(), String> - where - F: Future, - { - let host = if expose { "0.0.0.0" } else { "127.0.0.1" }; - - let addr: SocketAddr = format!("{}:{}", host, port) - .parse() - .expect("cannot parse hostname and port"); - let listener: TcpListener = TcpListener::bind(addr).await.expect("cannot bind to port"); - - if let Some(socket_addr_sender) = socket_addr_sender { - let addr = listener - .local_addr() - .expect("cannot read local TCP address"); - if let Err(e) = socket_addr_sender.send(addr) { - return Err(format!( - "Cannot send socket information to the test thread: {:?}", - e - )); - } +/// Starts a new instance of an HTTP mock server. You should never need to use this function +/// directly. Use it if you absolutely need to manage the low-level details of how the mock +/// server operates. +pub(crate) async fn start_server( + port: u16, + expose: bool, + state: &Arc, + socket_addr_sender: Option>, + print_access_log: bool, + shutdown: F, +) -> Result<(), String> +where + F: Future, +{ + let host = if expose { "0.0.0.0" } else { "127.0.0.1" }; + + let addr: SocketAddr = format!("{}:{}", host, port) + .parse() + .expect("cannot parse hostname and port"); + let listener: TcpListener = TcpListener::bind(addr).await.expect("cannot bind to port"); + + if let Some(socket_addr_sender) = socket_addr_sender { + let addr = listener + .local_addr() + .expect("cannot read local TCP address"); + if let Err(e) = socket_addr_sender.send(addr) { + return Err(format!( + "Cannot send socket information to the test thread: {:?}", + e + )); } + } - log::info!("Listening on {}", addr); + log::info!("Listening on {}", addr); - let shutdown = shutdown.shared(); + let shutdown = shutdown.shared(); - loop { - tokio::select! { + loop { + tokio::select! { accepted = listener.accept() => { match accepted { Ok((tcp_stream, remote_address)) => { @@ -583,315 +590,327 @@ async fn handle_server_request(req: HyperRequest, state: break; } } - } - - Ok(()) } - /// Maps a server response to a hyper response. - fn map_response(route_response: ServerResponse) -> Result>, String> { - let mut builder = HyperResponse::builder(); - builder = builder.status(route_response.status); - - for (key, value) in route_response.headers { - let name = HeaderName::from_str(&key); - if let Err(e) = name { - return Err(format!("Cannot create header from name: {}", e)); - } + Ok(()) +} - let value = HeaderValue::from_str(&value); - if let Err(e) = value { - return Err(format!("Cannot create header from value: {}", e)); - } +/// Maps a server response to a hyper response. +fn map_response( + route_response: ServerResponse, +) -> Result>, String> { + let mut builder = HyperResponse::builder(); + builder = builder.status(route_response.status); - let value = value.unwrap(); - let value = value.to_str(); - if let Err(e) = value { - return Err(format!("Cannot create header from value string: {}", e)); - } + for (key, value) in route_response.headers { + let name = HeaderName::from_str(&key); + if let Err(e) = name { + return Err(format!("Cannot create header from name: {}", e)); + } - builder = builder.header(name.unwrap(), value.unwrap()); + let value = HeaderValue::from_str(&value); + if let Err(e) = value { + return Err(format!("Cannot create header from value: {}", e)); } - let result = builder.body(Full::new(Bytes::from(route_response.body))); - if let Err(e) = result { - return Err(format!("Cannot create HTTP response: {}", e)); + let value = value.unwrap(); + let value = value.to_str(); + if let Err(e) = value { + return Err(format!("Cannot create header from value string: {}", e)); } - Ok(result.unwrap()) + builder = builder.header(name.unwrap(), value.unwrap()); } - /// Routes a request to the appropriate route handler. - async fn route_request( - state: &MockServerState, - request_header: &ServerRequestHeader, - body: Vec, - ) -> Result { - log::trace!("Routing incoming request: {:?}", request_header); - - if PING_PATH.is_match(&request_header.path) { - if let "GET" = request_header.method.as_str() { - return routes::ping(); - } - } + let result = builder.body( + Full::new(Bytes::from(route_response.body)) + .map_err(|never| match never {}) + .boxed(), + ); + if let Err(e) = result { + return Err(format!("Cannot create HTTP response: {}", e)); + } - if MOCKS_PATH.is_match(&request_header.path) { - match request_header.method.as_str() { - "POST" => return routes::add(state, body), - "DELETE" => return routes::delete_all_mocks(state), - _ => {} - } - } + Ok(result.unwrap()) +} - if MOCK_PATH.is_match(&request_header.path) { - let id = get_path_param(&MOCK_PATH, 1, &request_header.path); - if let Err(e) = id { - return Err(format!("Cannot parse id from path: {}", e)); - } - let id = id.unwrap(); +/// Routes a request to the appropriate route handler. +async fn route_request( + state: &MockServerState, + request_header: &ServerRequestHeader, + body: Vec, +) -> Result { + log::trace!("Routing incoming request: {:?}", request_header); - match request_header.method.as_str() { - "GET" => return routes::read_one(state, id), - "DELETE" => return routes::delete_one(state, id), - _ => {} - } + if PING_PATH.is_match(&request_header.path) { + if let "GET" = request_header.method.as_str() { + return routes::ping(); } + } - if PROXY_MATCHERS_PATH.is_match(&request_header.path) { - match request_header.method.as_str() { - "POST" => return routes::add_proxy_matcher(state, body), - "DELETE" => return routes::delete_all_proxy_matchers(state), - _ => {} - } + if MOCKS_PATH.is_match(&request_header.path) { + match request_header.method.as_str() { + "POST" => return routes::add(state, body), + "DELETE" => return routes::delete_all_mocks(state), + _ => {} } + } - if RECORDING_MATCHERS_PATH.is_match(&request_header.path) { - match request_header.method.as_str() { - "POST" => return routes::add_record_matcher(state, body), - "DELETE" => return routes::delete_all_recording_matchers(state), - _ => {} - } + if MOCK_PATH.is_match(&request_header.path) { + let id = get_path_param(&MOCK_PATH, 1, &request_header.path); + if let Err(e) = id { + return Err(format!("Cannot parse id from path: {}", e)); } + let id = id.unwrap(); - if VERIFY_PATH.is_match(&request_header.path) { - match request_header.method.as_str() { - "POST" => return routes::verify(state, body), - _ => {} - } + match request_header.method.as_str() { + "GET" => return routes::read_one(state, id), + "DELETE" => return routes::delete_one(state, id), + _ => {} } + } - if HISTORY_PATH.is_match(&request_header.path) { - match request_header.method.as_str() { - "DELETE" => return routes::delete_history(state), - _ => {} - } + if PROXY_MATCHERS_PATH.is_match(&request_header.path) { + match request_header.method.as_str() { + "POST" => return routes::add_proxy_matcher(state, body), + "DELETE" => return routes::delete_all_proxy_matchers(state), + _ => {} } - - routes::serve(state, request_header, body).await } - /// Get request path parameters. - fn get_path_param(regex: &Regex, idx: usize, path: &str) -> Result { - let cap = regex.captures(path); - if cap.is_none() { - return Err(format!( - "Error capturing parameter from request path: {}", - path - )); + if RECORDING_MATCHERS_PATH.is_match(&request_header.path) { + match request_header.method.as_str() { + "POST" => return routes::add_record_matcher(state, body), + "DELETE" => return routes::delete_all_recording_matchers(state), + _ => {} } - let cap = cap.unwrap(); + } - let id = cap.get(idx); - if id.is_none() { - return Err(format!( - "Error capturing resource id in request path: {}", - path - )); + if VERIFY_PATH.is_match(&request_header.path) { + match request_header.method.as_str() { + "POST" => return routes::verify(state, body), + _ => {} } - let id = id.unwrap().as_str(); + } - let id = id.parse::(); - if let Err(e) = id { - return Err(format!("Error parsing id as a number: {}", e)); + if HISTORY_PATH.is_match(&request_header.path) { + match request_header.method.as_str() { + "DELETE" => return routes::delete_history(state), + _ => {} } - let id = id.unwrap(); + } - Ok(id) + routes::serve(state, request_header, body).await +} + +/// Get request path parameters. +fn get_path_param(regex: &Regex, idx: usize, path: &str) -> Result { + let cap = regex.captures(path); + if cap.is_none() { + return Err(format!( + "Error capturing parameter from request path: {}", + path + )); + } + let cap = cap.unwrap(); + + let id = cap.get(idx); + if id.is_none() { + return Err(format!( + "Error capturing resource id in request path: {}", + path + )); } + let id = id.unwrap().as_str(); - /// Creates a default error response. - fn error_response(body: String) -> HyperResponse> { - HyperResponse::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Full::new(Bytes::from(body))) - .expect("Cannot build route error response") + let id = id.parse::(); + if let Err(e) = id { + return Err(format!("Error parsing id as a number: {}", e)); } + let id = id.unwrap(); - static BASE_PATH: &'static str = "/__httpmock__"; + Ok(id) +} + +/// Creates a default error response. +fn error_response(body: String) -> HyperResponse> { + HyperResponse::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body( + Full::new(body.into()) + .map_err(|never| match never {}) + .boxed(), + ) + .expect("Cannot build route error response") +} - lazy_static! { +static BASE_PATH: &'static str = "/__httpmock__"; + +lazy_static! { static ref PING_PATH: Regex = Regex::new(&format!(r"^{}/ping$", BASE_PATH)).unwrap(); static ref MOCKS_PATH: Regex = Regex::new(&format!(r"^{}/mocks$", BASE_PATH)).unwrap(); static ref MOCK_PATH: Regex = Regex::new(&format!(r"^{}/mocks/([0-9]+)$", BASE_PATH)).unwrap(); static ref HISTORY_PATH: Regex = Regex::new(&format!(r"^{}/history$", BASE_PATH)).unwrap(); static ref VERIFY_PATH: Regex = Regex::new(&format!(r"^{}/verify$", BASE_PATH)).unwrap(); - static ref PROXY_MATCHERS_PATH: Regex = Regex::new(&format!(r"^{}/proxy_matchers$", BASE_PATH)).unwrap(); - static ref RECORDING_MATCHERS_PATH: Regex = Regex::new(&format!(r"^{}/recording_matchers$", BASE_PATH)).unwrap(); + static ref PROXY_MATCHERS_PATH: Regex = + Regex::new(&format!(r"^{}/proxy_matchers$", BASE_PATH)).unwrap(); + static ref RECORDING_MATCHERS_PATH: Regex = + Regex::new(&format!(r"^{}/recording_matchers$", BASE_PATH)).unwrap(); } - #[cfg(test)] - mod test { - use std::collections::BTreeMap; +#[cfg(test)] +mod test { + use std::collections::BTreeMap; - use futures_util::TryStreamExt; - use http_body_util::BodyExt; + use futures_util::TryStreamExt; + use http_body_util::BodyExt; - use crate::server::{ - error_response, get_path_param, map_response, ServerResponse, HISTORY_PATH, MOCKS_PATH, - MOCK_PATH, PING_PATH, VERIFY_PATH, - }; - use crate::Regex; - use hyper::body::Bytes; - use hyper::Error; - - #[test] - fn route_regex_test() { - assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/1"), true); - assert_eq!( - MOCK_PATH.is_match("/__httpmock__/mocks/1295473892374"), - true - ); - assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/abc"), false); - assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks"), false); - assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/345345/test"), false); - assert_eq!( - MOCK_PATH.is_match("test/__httpmock__/mocks/345345/test"), - false - ); - - assert_eq!(PING_PATH.is_match("/__httpmock__/ping"), true); - assert_eq!( - PING_PATH.is_match("/__httpmock__/ping/1295473892374"), - false - ); - assert_eq!(PING_PATH.is_match("test/ping/1295473892374"), false); - - assert_eq!(VERIFY_PATH.is_match("/__httpmock__/verify"), true); - assert_eq!( - VERIFY_PATH.is_match("/__httpmock__/verify/1295473892374"), - false - ); - assert_eq!(VERIFY_PATH.is_match("test/verify/1295473892374"), false); + use crate::server::{ + error_response, get_path_param, map_response, ServerResponse, HISTORY_PATH, MOCKS_PATH, + MOCK_PATH, PING_PATH, VERIFY_PATH, + }; + use crate::Regex; + use hyper::body::Bytes; + use hyper::Error; + + #[test] + fn route_regex_test() { + assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/1"), true); + assert_eq!( + MOCK_PATH.is_match("/__httpmock__/mocks/1295473892374"), + true + ); + assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/abc"), false); + assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks"), false); + assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/345345/test"), false); + assert_eq!( + MOCK_PATH.is_match("test/__httpmock__/mocks/345345/test"), + false + ); + + assert_eq!(PING_PATH.is_match("/__httpmock__/ping"), true); + assert_eq!( + PING_PATH.is_match("/__httpmock__/ping/1295473892374"), + false + ); + assert_eq!(PING_PATH.is_match("test/ping/1295473892374"), false); + + assert_eq!(VERIFY_PATH.is_match("/__httpmock__/verify"), true); + assert_eq!( + VERIFY_PATH.is_match("/__httpmock__/verify/1295473892374"), + false + ); + assert_eq!(VERIFY_PATH.is_match("test/verify/1295473892374"), false); + + assert_eq!(HISTORY_PATH.is_match("/__httpmock__/history"), true); + println!("{:?}", HISTORY_PATH.as_str()); + + assert_eq!( + HISTORY_PATH.is_match("/__httpmock__/history/1295473892374"), + false + ); + assert_eq!(HISTORY_PATH.is_match("test/history/1295473892374"), false); + + assert_eq!(MOCKS_PATH.is_match("/__httpmock__/mocks"), true); + assert_eq!(MOCKS_PATH.is_match("/__httpmock__/mocks/5"), false); + assert_eq!(MOCKS_PATH.is_match("test/__httpmock__/mocks/5"), false); + assert_eq!(MOCKS_PATH.is_match("test/__httpmock__/mocks/567"), false); + } - assert_eq!(HISTORY_PATH.is_match("/__httpmock__/history"), true); - println!("{:?}", HISTORY_PATH.as_str()); + /// Make sure passing an empty string to the error response does not result in an error. + #[test] + fn error_response_test() { + let res = error_response("test".into()); + let (parts, body) = res.into_parts(); - assert_eq!( - HISTORY_PATH.is_match("/__httpmock__/history/1295473892374"), - false - ); - assert_eq!(HISTORY_PATH.is_match("test/history/1295473892374"), false); + let body = async_std::task::block_on(async { + return match BodyExt::collect(body).await { + Ok(collected_bytes) => collected_bytes.to_bytes(), + Err(e) => panic!(e), + }; + }); - assert_eq!(MOCKS_PATH.is_match("/__httpmock__/mocks"), true); - assert_eq!(MOCKS_PATH.is_match("/__httpmock__/mocks/5"), false); - assert_eq!(MOCKS_PATH.is_match("test/__httpmock__/mocks/5"), false); - assert_eq!(MOCKS_PATH.is_match("test/__httpmock__/mocks/567"), false); - } + assert_eq!( + String::from_utf8(body.to_vec()).unwrap(), + "test".to_string() + ) + } - /// Make sure passing an empty string to the error response does not result in an error. - #[test] - fn error_response_test() { - let res = error_response("test".into()); - let (parts, body) = res.into_parts(); - - let body = async_std::task::block_on(async { - return match BodyExt::collect(body).await { - Ok(collected_bytes) => collected_bytes.to_bytes(), - Err(e) => panic!(e), - }; - }); - - assert_eq!( - String::from_utf8(body.to_vec()).unwrap(), - "test".to_string() - ) - } + /// Makes sure an error is return if there is a header parsing error + #[test] + fn response_header_key_parsing_error_test() { + // Arrange + let mut headers = Vec::new(); + headers.push((";;;".to_string(), ";;;".to_string())); - /// Makes sure an error is return if there is a header parsing error - #[test] - fn response_header_key_parsing_error_test() { - // Arrange - let mut headers = Vec::new(); - headers.push((";;;".to_string(), ";;;".to_string())); - - let res = ServerResponse { - body: Vec::new(), - status: 500, - headers, - }; + let res = ServerResponse { + body: Vec::new(), + status: 500, + headers, + }; - // Act - let result = map_response(res); - - // Assert - assert_eq!(result.is_err(), true); - assert_eq!( - result - .err() - .unwrap() - .contains("Cannot create header from name"), - true - ); - } + // Act + let result = map_response(res); + + // Assert + assert_eq!(result.is_err(), true); + assert_eq!( + result + .err() + .unwrap() + .contains("Cannot create header from name"), + true + ); + } - #[test] - fn get_path_param_regex_error_test() { - // Arrange - let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); - - // Act - let result = get_path_param(&re, 0, ""); - - // Assert - assert_eq!(result.is_err(), true); - assert_eq!( - result - .err() - .unwrap() - .contains("Error capturing parameter from request path"), - true - ); - } + #[test] + fn get_path_param_regex_error_test() { + // Arrange + let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); + + // Act + let result = get_path_param(&re, 0, ""); + + // Assert + assert_eq!(result.is_err(), true); + assert_eq!( + result + .err() + .unwrap() + .contains("Error capturing parameter from request path"), + true + ); + } - #[test] - fn get_path_param_index_error_test() { - // Arrange - let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); + #[test] + fn get_path_param_index_error_test() { + // Arrange + let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); - // Act - let result = get_path_param(&re, 5, "/__httpmock__/mocks/5"); + // Act + let result = get_path_param(&re, 5, "/__httpmock__/mocks/5"); - // Assert - assert_eq!(result.is_err(), true); - assert_eq!( - "Error capturing resource id in request path: /__httpmock__/mocks/5", - result.err().unwrap() - ); - } + // Assert + assert_eq!(result.is_err(), true); + assert_eq!( + "Error capturing resource id in request path: /__httpmock__/mocks/5", + result.err().unwrap() + ); + } - #[test] - fn get_path_param_number_error_test() { - // Arrange - let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); + #[test] + fn get_path_param_number_error_test() { + // Arrange + let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); - // Act - let result = get_path_param(&re, 0, "/__httpmock__/mocks/9999999999999999999999999"); + // Act + let result = get_path_param(&re, 0, "/__httpmock__/mocks/9999999999999999999999999"); - // Assert - assert_eq!(result.is_err(), true); - assert_eq!( - "Error parsing id as a number: invalid digit found in string", - result.err().unwrap() - ); - } + // Assert + assert_eq!(result.is_err(), true); + assert_eq!( + "Error parsing id as a number: invalid digit found in string", + result.err().unwrap() + ); } +} diff --git a/src/server/proxy.rs b/src/server/proxy.rs index 4c26910..f4bb744 100644 --- a/src/server/proxy.rs +++ b/src/server/proxy.rs @@ -1,23 +1,20 @@ -use hyper::{ - body::Incoming as IncomingBody, Request as HyperRequest -}; -use hyper_util::rt::tokio::TokioIo; use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; use hyper::body::{Body, Bytes}; -use hyper::{Result as HyperResult, Response as HyperResponse}; +use hyper::{body::Incoming as IncomingBody, Request as HyperRequest}; +use hyper::{Response as HyperResponse, Result as HyperResult}; +use hyper_util::rt::tokio::TokioIo; use std::net::SocketAddr; - - +use hyper::client::conn::http1::Builder; use hyper::server::conn::http1; use hyper::service::service_fn; use hyper::upgrade::Upgraded; use hyper::{Method, Request, Response}; -use hyper::client::conn::http1::Builder; use tokio::net::TcpStream; - -pub(crate) async fn try_proxy_request(req: HyperRequest) -> Result>>, hyper::Error> { +pub(crate) async fn try_proxy_request( + req: HyperRequest, +) -> Result>>, hyper::Error> { if hyper::Method::CONNECT == req.method() { // Received an HTTP request like: // ``` @@ -75,20 +72,22 @@ pub(crate) async fn try_proxy_request(req: HyperRequest) } else { Ok(None) } - } fn host_addr(uri: &hyper::http::Uri) -> Option { uri.authority().and_then(|auth| Some(auth.to_string())) } - -fn full>(chunk: T) -> Full{ +fn full>(chunk: T) -> BoxBody { Full::new(chunk.into()) + .map_err(|never| match never {}) + .boxed() } -fn empty() -> Full { - Full::::new(Bytes::new()) +fn empty() -> BoxBody { + Empty::::new() + .map_err(|never| match never {}) + .boxed() } // Create a TCP connection to host:port, build a tunnel between the connection and diff --git a/src/server/web/handlers.rs b/src/server/web/handlers.rs index 31135f1..12b1f2b 100644 --- a/src/server/web/handlers.rs +++ b/src/server/web/handlers.rs @@ -2,7 +2,10 @@ use std::collections::BTreeMap; use std::str::FromStr; use std::sync::Arc; -use crate::common::data::{ActiveMock, ActiveProxyMatcher, ClosestMatch, HttpMockRequest, Mismatch, MockDefinition, MockServerHttpResponse, ProxyMatcherRef, RecordingMatcherRef, RequestRequirements}; +use crate::common::data::{ + ActiveMock, ActiveProxyMatcher, ClosestMatch, HttpMockRequest, Mismatch, MockDefinition, + MockServerHttpResponse, ProxyMatcherRef, RecordingMatcherRef, RequestRequirements, +}; use crate::server::matchers::Matcher; use crate::server::util::{StringTreeMapExtension, TreeMapExtension}; use crate::server::MockServerState; @@ -98,7 +101,7 @@ pub(crate) fn add_proxy_matcher( let mut proxy_matchers = state.proxy_matchers.lock().unwrap(); proxy_matchers.insert(id, ActiveProxyMatcher::new(id, req.clone())); - Ok(ProxyMatcherRef{id}) + Ok(ProxyMatcherRef { id }) } /// Deletes all proxy matchers. @@ -125,10 +128,9 @@ pub(crate) fn add_recording_matcher( let id = state.new_recording_matcher_id(); log::debug!("Adding new recording matcher with ID={}", id); - Ok(RecordingMatcherRef{id}) + Ok(RecordingMatcherRef { id }) } - /// Deletes all recording matchers. pub(crate) fn delete_all_recording_matchers(state: &MockServerState) { let mut recording_matchers = state.recording_matchers.lock().unwrap(); diff --git a/src/server/web/routes.rs b/src/server/web/routes.rs index 6f8e373..33953ce 100644 --- a/src/server/web/routes.rs +++ b/src/server/web/routes.rs @@ -2,7 +2,10 @@ use std::collections::BTreeMap; use serde::Serialize; -use crate::common::data::{ErrorResponse, HttpMockRequest, MockDefinition, MockRef, MockServerHttpResponse, ProxyMatcherRef, RecordingMatcherRef, RequestRequirements}; +use crate::common::data::{ + ErrorResponse, HttpMockRequest, MockDefinition, MockRef, MockServerHttpResponse, + ProxyMatcherRef, RecordingMatcherRef, RequestRequirements, +}; use crate::server::web::handlers; use crate::server::{MockServerState, ServerRequestHeader, ServerResponse}; @@ -83,9 +86,10 @@ pub(crate) fn verify(state: &MockServerState, body: Vec) -> Result) -> Result { +pub(crate) fn add_proxy_matcher( + state: &MockServerState, + body: Vec, +) -> Result { let def: serde_json::Result = serde_json::from_slice(&body); if let Err(e) = def { return create_json_response(500, None, ErrorResponse::new(&e)); @@ -102,7 +106,10 @@ pub(crate) fn delete_all_proxy_matchers(state: &MockServerState) -> Result) -> Result { +pub(crate) fn add_record_matcher( + state: &MockServerState, + body: Vec, +) -> Result { let def: serde_json::Result = serde_json::from_slice(&body); if let Err(e) = def { return create_json_response(500, None, ErrorResponse::new(&e)); @@ -114,7 +121,9 @@ pub(crate) fn add_record_matcher(state: &MockServerState, body: Vec) -> Resu } } -pub(crate) fn delete_all_recording_matchers(state: &MockServerState) -> Result { +pub(crate) fn delete_all_recording_matchers( + state: &MockServerState, +) -> Result { handlers::delete_all_proxy_matchers(state); create_response(202, None, None) } diff --git a/tests/examples/proxy_tests.rs b/tests/examples/proxy_tests.rs index aea7973..eefcaaa 100644 --- a/tests/examples/proxy_tests.rs +++ b/tests/examples/proxy_tests.rs @@ -1,5 +1,6 @@ use httpmock::prelude::*; use isahc::prelude::*; +use std::io::Read; #[test] fn proxy_test() { @@ -25,5 +26,6 @@ fn proxy_test() { // Act let mut response = client.get("https://www.google.com").unwrap(); + assert_eq!(response.text().unwrap(), ""); assert_eq!(response.status(), 200); }