feat(net): add tracing
This commit is contained in:
parent
6e5ea556a9
commit
436ad890d8
4 changed files with 64 additions and 44 deletions
|
@ -161,6 +161,7 @@ pub const fn new() -> Net {
|
|||
}
|
||||
|
||||
/// Creates a new `MockNet` for use in tests.
|
||||
#[tracing::instrument]
|
||||
pub fn mock() -> MockNet {
|
||||
Default::default()
|
||||
}
|
||||
|
|
|
@ -23,8 +23,10 @@ pub enum Error {
|
|||
Url(url::ParseError),
|
||||
|
||||
/// There was network request that doesn't match any that were expected
|
||||
#[display("Unexpected request: {0}", 0.to_string())]
|
||||
UnexpectedMockRequest(Request),
|
||||
#[display("Unexpected request: {} {}", request.method(), request.url())]
|
||||
UnexpectedMockRequest {
|
||||
request: Request,
|
||||
},
|
||||
|
||||
/// There was an error accessing the list of expected requests.
|
||||
RwLockLocked,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
|
@ -33,36 +32,38 @@ struct Plan {
|
|||
response: reqwest::Response,
|
||||
}
|
||||
impl Plan {
|
||||
#[tracing::instrument]
|
||||
fn matches(&self, request: &Request) -> bool {
|
||||
let url = request.url();
|
||||
self.match_request.iter().all(|criteria| match criteria {
|
||||
MatchRequest::Body(body) => {
|
||||
request.body().and_then(reqwest::Body::as_bytes) == Some(body)
|
||||
}
|
||||
MatchRequest::Header { name, value } => {
|
||||
request
|
||||
.headers()
|
||||
.iter()
|
||||
.any(|(request_header_name, request_header_value)| {
|
||||
let Ok(request_header_value) = request_header_value.to_str() else {
|
||||
return false;
|
||||
};
|
||||
request_header_name.as_str() == name && request_header_value == value
|
||||
})
|
||||
}
|
||||
MatchRequest::Method(method) => request.method() == http::Method::from(method),
|
||||
MatchRequest::Scheme(scheme) => url.scheme() == scheme,
|
||||
MatchRequest::Host(host) => url.host_str() == Some(host),
|
||||
MatchRequest::Path(path) => url.path() == path,
|
||||
MatchRequest::Fragment(fragment) => url.fragment() == Some(fragment),
|
||||
MatchRequest::Query { name, value } => {
|
||||
url.query_pairs()
|
||||
.into_iter()
|
||||
.any(|(request_query_name, request_query_value)| {
|
||||
let is_match =
|
||||
self.match_request.iter().all(|criteria| match criteria {
|
||||
MatchRequest::Body(body) => {
|
||||
request.body().and_then(reqwest::Body::as_bytes) == Some(body)
|
||||
}
|
||||
MatchRequest::Header { name, value } => {
|
||||
request
|
||||
.headers()
|
||||
.iter()
|
||||
.any(|(request_header_name, request_header_value)| {
|
||||
let Ok(request_header_value) = request_header_value.to_str() else {
|
||||
return false;
|
||||
};
|
||||
request_header_name.as_str() == name && request_header_value == value
|
||||
})
|
||||
}
|
||||
MatchRequest::Method(method) => request.method() == http::Method::from(method),
|
||||
MatchRequest::Scheme(scheme) => url.scheme() == scheme,
|
||||
MatchRequest::Host(host) => url.host_str() == Some(host),
|
||||
MatchRequest::Path(path) => url.path() == path,
|
||||
MatchRequest::Fragment(fragment) => url.fragment() == Some(fragment),
|
||||
MatchRequest::Query { name, value } => url.query_pairs().into_iter().any(
|
||||
|(request_query_name, request_query_value)| {
|
||||
request_query_name.as_ref() == name && request_query_value.as_ref() == value
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
});
|
||||
tracing::debug!(?is_match);
|
||||
is_match
|
||||
}
|
||||
}
|
||||
impl Display for Plan {
|
||||
|
@ -129,17 +130,16 @@ impl Net {
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn send(&self, request: impl Into<RequestBuilder>) -> Result<Response> {
|
||||
let request: RequestBuilder = request.into();
|
||||
tracing::debug!(?request);
|
||||
let Some(plans) = &self.plans else {
|
||||
return request.into().send().await.map_err(Error::from);
|
||||
tracing::debug!("no plans - sending real request");
|
||||
return request.send().await.map_err(Error::from);
|
||||
};
|
||||
let request = request.into().build()?;
|
||||
eprintln!(
|
||||
"? {} {} {:?}",
|
||||
request.method(),
|
||||
request.url(),
|
||||
request.headers()
|
||||
);
|
||||
tracing::debug!("build request");
|
||||
let request = request.build()?;
|
||||
let index = plans
|
||||
.lock()
|
||||
.await
|
||||
|
@ -150,15 +150,19 @@ impl Net {
|
|||
match index {
|
||||
Some(i) => {
|
||||
let plan = plans.lock().await.borrow_mut().remove(i);
|
||||
eprintln!("- matched: {plan}");
|
||||
let response = plan.response;
|
||||
if response.status().is_success() {
|
||||
tracing::debug!(?request, "matched success response");
|
||||
Ok(response)
|
||||
} else {
|
||||
tracing::debug!(?request, "matched error response");
|
||||
Err(crate::net::Error::ResponseError { response })
|
||||
}
|
||||
}
|
||||
None => Err(Error::UnexpectedMockRequest(request)),
|
||||
None => {
|
||||
tracing::warn!(?request, "unexpected mock request");
|
||||
Err(Error::UnexpectedMockRequest { request })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,14 +380,17 @@ impl<'net> ReqBuilder<'net> {
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn send(self) -> Result<Response> {
|
||||
let client = self.net.client();
|
||||
// URL
|
||||
let mut url = self.url;
|
||||
tracing::trace!(?url);
|
||||
// Query Parameters
|
||||
if !self.query.is_empty() {
|
||||
url.push('?');
|
||||
for (i, (name, value)) in self.query.into_iter().enumerate() {
|
||||
tracing::trace!(?name, ?value, "query parameters");
|
||||
if i > 0 {
|
||||
url.push('&');
|
||||
}
|
||||
|
@ -391,6 +398,7 @@ impl<'net> ReqBuilder<'net> {
|
|||
url.push('=');
|
||||
url.push_str(&value);
|
||||
}
|
||||
tracing::trace!(?url, "with query parameters");
|
||||
}
|
||||
// Method
|
||||
let mut req = match self.method {
|
||||
|
@ -409,7 +417,7 @@ impl<'net> ReqBuilder<'net> {
|
|||
if let Some(bytes) = self.body {
|
||||
req = req.body(bytes);
|
||||
}
|
||||
|
||||
tracing::debug!(?req);
|
||||
self.net.send(req).await
|
||||
}
|
||||
|
||||
|
@ -588,6 +596,7 @@ impl MockNet {
|
|||
/// # }
|
||||
/// ```
|
||||
pub fn reset(&self) {
|
||||
tracing::debug!("reset plans");
|
||||
self.plans.take();
|
||||
}
|
||||
}
|
||||
|
@ -602,19 +611,23 @@ impl From<MockNet> for Net {
|
|||
}
|
||||
|
||||
impl Drop for MockNet {
|
||||
#[tracing::instrument]
|
||||
fn drop(&mut self) {
|
||||
let unused = self.plans.take();
|
||||
if unused.is_empty() {
|
||||
tracing::trace!("no unused expected requests");
|
||||
return; // all good
|
||||
}
|
||||
panic_with_unused_plans(unused);
|
||||
}
|
||||
}
|
||||
impl Drop for Net {
|
||||
#[tracing::instrument]
|
||||
fn drop(&mut self) {
|
||||
if let Some(plans) = &self.plans {
|
||||
let unused = plans.try_lock().expect("lock plans").take();
|
||||
if unused.is_empty() {
|
||||
tracing::trace!("no unused expected requests");
|
||||
return; // all good
|
||||
}
|
||||
panic_with_unused_plans(unused);
|
||||
|
@ -672,9 +685,11 @@ impl std::error::Error for MockError {}
|
|||
|
||||
pub trait WhenState {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WhenBuildRequest;
|
||||
impl WhenState for WhenBuildRequest {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WhenBuildResponse;
|
||||
impl WhenState for WhenBuildResponse {}
|
||||
|
||||
|
@ -737,6 +752,7 @@ impl<'net> WhenRequest<'net, WhenBuildRequest> {
|
|||
self._url(NetMethod::Patch, url)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn _url(mut self, method: NetMethod, url: impl Into<String>) -> Self {
|
||||
self.match_on.push(MatchRequest::Method(method));
|
||||
match Url::parse(&url.into()) {
|
||||
|
@ -774,6 +790,7 @@ impl<'net> WhenRequest<'net, WhenBuildRequest> {
|
|||
self.error.replace(err.into());
|
||||
}
|
||||
}
|
||||
tracing::debug!(match_on = ?self.match_on, error = ?self.error);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -214,12 +214,12 @@ async fn test_get_wrong_url() {
|
|||
|
||||
//when
|
||||
let_assert!(
|
||||
Err(Error::UnexpectedMockRequest(invalid_request)) =
|
||||
Err(Error::UnexpectedMockRequest { request }) =
|
||||
net.send(client.get("https://some.other.url/")).await
|
||||
);
|
||||
|
||||
//then
|
||||
assert_eq!(invalid_request.url().to_string(), "https://some.other.url/");
|
||||
assert_eq!(request.url().to_string(), "https://some.other.url/");
|
||||
|
||||
// remove pending unmatched request - we never meant to match against it
|
||||
let mock_net = MockNet::try_from(net).await.expect("recover net");
|
||||
|
@ -329,7 +329,7 @@ async fn test_post_by_header_wrong_value() {
|
|||
.await;
|
||||
|
||||
//then
|
||||
let_assert!(Err(kxio::net::Error::UnexpectedMockRequest(_)) = response);
|
||||
let_assert!(Err(kxio::net::Error::UnexpectedMockRequest { request: _ }) = response);
|
||||
|
||||
MockNet::try_from(net).await.expect("recover mock").reset();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue