From aad02be6cb06f3f2968c52d38467df1948fffab0 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 10 Nov 2024 09:44:30 +0000 Subject: [PATCH] feat(net): be more permisive in what parameters are accepted --- examples/get.rs | 23 +++++++++----- src/net/result.rs | 2 ++ src/net/system.rs | 77 ++++++++++++++++++++++++++++++++--------------- tests/net.rs | 16 +++++----- 4 files changed, 78 insertions(+), 40 deletions(-) diff --git a/examples/get.rs b/examples/get.rs index 52d7ea7..f97cc29 100644 --- a/examples/get.rs +++ b/examples/get.rs @@ -110,35 +110,44 @@ mod tests { // When `net` goes out of scope it will check that all the expected network requests (see // `net.on(...)` below) were all made. If there are any that were not made, the test will // be failed. If you want to avoid this, then call `net.reset()` before your test ends. - let net: kxio::net::MockNet = kxio::net::mock(); + let mock_net: kxio::net::MockNet = kxio::net::mock(); let url = "http://localhost:8080"; // declare what response should be made for a given request let response: http::Response<&str> = - net.response().body("contents").expect("response body"); - let request = net.client().get(url).build().expect("request"); - net.on(request) + mock_net.response().body("contents").expect("response body"); + let request = mock_net.client().get(url).build().expect("request"); + mock_net + .on(request) // By default, the METHOD and URL must match, equivalent to: //.match_on(vec![MatchOn::Method, MatchOn::Url]) - .respond(response.into()) + .respond(response) .expect("mock"); // Create a temporary directory that will be deleted with `fs` goes out of scope let fs = kxio::fs::temp().expect("temp fs"); let file_path = fs.base().join("foo"); + // Create a [Net] from the [MockNet] to pass to the system under tets + let net = kxio::net::Net::from(mock_net); + //when // Pass the file sytsem and network abstractions to the code to be tested - download_and_save_to_file(url, &file_path, &fs, &net.into()) + download_and_save_to_file(url, &file_path, &fs, &net) .await .expect("system under test"); //then - // Open a file and read it + // Read the file let file = fs.file(&file_path); let reader = file.reader().expect("reader"); let contents = reader.as_str(); assert_eq!(contents, "contents"); + + // not needed for this test, but should it be needed, we can avoid checking for any + // unconsumed request matches. + let mock_net = kxio::net::MockNet::try_from(net).expect("recover mock"); + mock_net.reset().expect("reset mock"); } } diff --git a/src/net/result.rs b/src/net/result.rs index 0397dba..18295e4 100644 --- a/src/net/result.rs +++ b/src/net/result.rs @@ -9,6 +9,8 @@ pub enum Error { #[display("Unexpected request: {0}", 0.to_string())] UnexpectedMockRequest(reqwest::Request), RwLockLocked, + Http(http::Error), + NetIsNotAMock, } impl std::error::Error for Error {} impl Clone for Error { diff --git a/src/net/system.rs b/src/net/system.rs index 247f9b7..1ed229e 100644 --- a/src/net/system.rs +++ b/src/net/system.rs @@ -1,5 +1,7 @@ // -use std::{marker::PhantomData, ops::Deref, sync::RwLock}; +use std::{marker::PhantomData, sync::RwLock}; + +use reqwest::Client; use super::{Error, Result}; @@ -56,29 +58,58 @@ impl Net { Default::default() } - pub async fn send(&self, request: reqwest::RequestBuilder) -> Result { + pub async fn send( + &self, + request: impl Into, + ) -> Result { match &self.mock { Some(mock) => mock.send(request).await, None => self.inner.send(request).await, } } } +impl TryFrom for MockNet { + type Error = super::Error; + + fn try_from(net: Net) -> std::result::Result { + match net.mock { + Some(inner_mock) => Ok(MockNet { inner: inner_mock }), + None => Err(Self::Error::NetIsNotAMock), + } + } +} #[derive(Debug)] pub struct MockNet { inner: InnerNet, } -impl Deref for MockNet { - type Target = InnerNet; - - fn deref(&self) -> &Self::Target { - &self.inner +impl MockNet { + pub fn client(&self) -> Client { + Default::default() + } + pub fn on(&self, request: impl Into) -> OnRequest { + self.inner.on(request) + } + /// Creates a [http::response::Builder] to be extended and returned by a mocked network request. + pub fn response(&self) -> http::response::Builder { + Default::default() + } + pub async fn send( + &self, + request: impl Into, + ) -> Result { + self.inner.send(request).await + } + pub fn reset(&self) -> Result<()> { + self.inner.reset() } } impl From for Net { fn from(mock_net: MockNet) -> Self { Self { inner: InnerNet::::new(), + // keep the original `inner` around to allow it's Drop impelmentation to run when we go + // out of scope at the end of the test mock: Some(mock_net.inner), } } @@ -97,16 +128,14 @@ impl InnerNet { } } - pub async fn send(&self, request: reqwest::RequestBuilder) -> Result { - request.send().await.map_err(Error::from) + pub async fn send( + &self, + request: impl Into, + ) -> Result { + request.into().send().await.map_err(Error::from) } } -impl InnerNet { - pub fn client(&self) -> reqwest::Client { - Default::default() - } -} impl InnerNet { const fn new() -> Self { Self { @@ -115,8 +144,11 @@ impl InnerNet { } } - pub async fn send(&self, request: reqwest::RequestBuilder) -> Result { - let request = request.build()?; + pub async fn send( + &self, + request: impl Into, + ) -> Result { + let request = request.into().build()?; let read_plans = self.plans.read().map_err(|_| Error::RwLockLocked)?; let index = read_plans.iter().position(|plan| { // METHOD @@ -158,15 +190,10 @@ impl InnerNet { } } - /// Creates a [http::response::Builder] to be extended and returned by a mocked network request. - pub fn response(&self) -> http::response::Builder { - Default::default() - } - - pub fn on(&self, request: reqwest::Request) -> OnRequest { + pub fn on(&self, request: impl Into) -> OnRequest { OnRequest { net: self, - request, + request: request.into(), match_on: vec![MatchOn::Method, MatchOn::Url], } } @@ -214,7 +241,7 @@ impl<'net> OnRequest<'net> { match_on, } } - pub fn respond(self, response: reqwest::Response) -> Result<()> { - self.net._on(self.request, response, self.match_on) + pub fn respond(self, response: impl Into) -> Result<()> { + self.net._on(self.request, response.into(), self.match_on) } } diff --git a/tests/net.rs b/tests/net.rs index 248dc56..5648463 100644 --- a/tests/net.rs +++ b/tests/net.rs @@ -17,7 +17,7 @@ async fn test_get_url() { .expect("request body"); net.on(request) - .respond(my_response.into()) + .respond(my_response) .expect("on request, respond"); //when @@ -46,7 +46,7 @@ async fn test_get_wrong_url() { .expect("request body"); net.on(request) - .respond(my_response.into()) + .respond(my_response) .expect("on request, respond"); //when @@ -77,7 +77,7 @@ async fn test_post_url() { .expect("request body"); net.on(request) - .respond(my_response.into()) + .respond(my_response) .expect("on request, respond"); //when @@ -110,7 +110,7 @@ async fn test_post_by_method() { MatchOn::Method, // MatchOn::Url ]) - .respond(my_response.into()) + .respond(my_response) .expect("on request, respond"); //when @@ -144,7 +144,7 @@ async fn test_post_by_url() { // MatchOn::Method, MatchOn::Url, ]) - .respond(my_response.into()) + .respond(my_response) .expect("on request, respond"); //when @@ -183,7 +183,7 @@ async fn test_post_by_body() { // MatchOn::Url MatchOn::Body, ]) - .respond(my_response.into()) + .respond(my_response) .expect("on request, respond"); //when @@ -226,7 +226,7 @@ async fn test_post_by_headers() { // MatchOn::Url MatchOn::Headers, ]) - .respond(my_response.into()) + .respond(my_response) .expect("on request, respond"); //when @@ -265,7 +265,7 @@ async fn test_unused_post() { .expect("request body"); net.on(request) - .respond(my_response.into()) + .respond(my_response) .expect("on request, respond"); //when