feat(net): be more permisive in what parameters are accepted
All checks were successful
Rust / build (map[name:nightly]) (push) Successful in 1m57s
Rust / build (map[name:stable]) (push) Successful in 2m12s
Release Please / Release-plz (push) Successful in 1m20s

This commit is contained in:
Paul Campbell 2024-11-10 09:44:30 +00:00
parent 7285cff6e7
commit aad02be6cb
4 changed files with 78 additions and 40 deletions

View file

@ -110,35 +110,44 @@ mod tests {
// When `net` goes out of scope it will check that all the expected network requests (see // 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 // `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. // 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"; let url = "http://localhost:8080";
// declare what response should be made for a given request // declare what response should be made for a given request
let response: http::Response<&str> = let response: http::Response<&str> =
net.response().body("contents").expect("response body"); mock_net.response().body("contents").expect("response body");
let request = net.client().get(url).build().expect("request"); let request = mock_net.client().get(url).build().expect("request");
net.on(request) mock_net
.on(request)
// By default, the METHOD and URL must match, equivalent to: // By default, the METHOD and URL must match, equivalent to:
//.match_on(vec![MatchOn::Method, MatchOn::Url]) //.match_on(vec![MatchOn::Method, MatchOn::Url])
.respond(response.into()) .respond(response)
.expect("mock"); .expect("mock");
// Create a temporary directory that will be deleted with `fs` goes out of scope // Create a temporary directory that will be deleted with `fs` goes out of scope
let fs = kxio::fs::temp().expect("temp fs"); let fs = kxio::fs::temp().expect("temp fs");
let file_path = fs.base().join("foo"); 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 //when
// Pass the file sytsem and network abstractions to the code to be tested // 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 .await
.expect("system under test"); .expect("system under test");
//then //then
// Open a file and read it // Read the file
let file = fs.file(&file_path); let file = fs.file(&file_path);
let reader = file.reader().expect("reader"); let reader = file.reader().expect("reader");
let contents = reader.as_str(); let contents = reader.as_str();
assert_eq!(contents, "contents"); 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");
} }
} }

View file

@ -9,6 +9,8 @@ pub enum Error {
#[display("Unexpected request: {0}", 0.to_string())] #[display("Unexpected request: {0}", 0.to_string())]
UnexpectedMockRequest(reqwest::Request), UnexpectedMockRequest(reqwest::Request),
RwLockLocked, RwLockLocked,
Http(http::Error),
NetIsNotAMock,
} }
impl std::error::Error for Error {} impl std::error::Error for Error {}
impl Clone for Error { impl Clone for Error {

View file

@ -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}; use super::{Error, Result};
@ -56,29 +58,58 @@ impl Net {
Default::default() Default::default()
} }
pub async fn send(&self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> { pub async fn send(
&self,
request: impl Into<reqwest::RequestBuilder>,
) -> Result<reqwest::Response> {
match &self.mock { match &self.mock {
Some(mock) => mock.send(request).await, Some(mock) => mock.send(request).await,
None => self.inner.send(request).await, None => self.inner.send(request).await,
} }
} }
} }
impl TryFrom<Net> for MockNet {
type Error = super::Error;
fn try_from(net: Net) -> std::result::Result<Self, Self::Error> {
match net.mock {
Some(inner_mock) => Ok(MockNet { inner: inner_mock }),
None => Err(Self::Error::NetIsNotAMock),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct MockNet { pub struct MockNet {
inner: InnerNet<Mocked>, inner: InnerNet<Mocked>,
} }
impl Deref for MockNet { impl MockNet {
type Target = InnerNet<Mocked>; pub fn client(&self) -> Client {
Default::default()
fn deref(&self) -> &Self::Target { }
&self.inner pub fn on(&self, request: impl Into<reqwest::Request>) -> 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<reqwest::RequestBuilder>,
) -> Result<reqwest::Response> {
self.inner.send(request).await
}
pub fn reset(&self) -> Result<()> {
self.inner.reset()
} }
} }
impl From<MockNet> for Net { impl From<MockNet> for Net {
fn from(mock_net: MockNet) -> Self { fn from(mock_net: MockNet) -> Self {
Self { Self {
inner: InnerNet::<Unmocked>::new(), inner: InnerNet::<Unmocked>::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), mock: Some(mock_net.inner),
} }
} }
@ -97,16 +128,14 @@ impl InnerNet<Unmocked> {
} }
} }
pub async fn send(&self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> { pub async fn send(
request.send().await.map_err(Error::from) &self,
request: impl Into<reqwest::RequestBuilder>,
) -> Result<reqwest::Response> {
request.into().send().await.map_err(Error::from)
} }
} }
impl<T: NetType> InnerNet<T> {
pub fn client(&self) -> reqwest::Client {
Default::default()
}
}
impl InnerNet<Mocked> { impl InnerNet<Mocked> {
const fn new() -> Self { const fn new() -> Self {
Self { Self {
@ -115,8 +144,11 @@ impl InnerNet<Mocked> {
} }
} }
pub async fn send(&self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> { pub async fn send(
let request = request.build()?; &self,
request: impl Into<reqwest::RequestBuilder>,
) -> Result<reqwest::Response> {
let request = request.into().build()?;
let read_plans = self.plans.read().map_err(|_| Error::RwLockLocked)?; let read_plans = self.plans.read().map_err(|_| Error::RwLockLocked)?;
let index = read_plans.iter().position(|plan| { let index = read_plans.iter().position(|plan| {
// METHOD // METHOD
@ -158,15 +190,10 @@ impl InnerNet<Mocked> {
} }
} }
/// Creates a [http::response::Builder] to be extended and returned by a mocked network request. pub fn on(&self, request: impl Into<reqwest::Request>) -> OnRequest {
pub fn response(&self) -> http::response::Builder {
Default::default()
}
pub fn on(&self, request: reqwest::Request) -> OnRequest {
OnRequest { OnRequest {
net: self, net: self,
request, request: request.into(),
match_on: vec![MatchOn::Method, MatchOn::Url], match_on: vec![MatchOn::Method, MatchOn::Url],
} }
} }
@ -214,7 +241,7 @@ impl<'net> OnRequest<'net> {
match_on, match_on,
} }
} }
pub fn respond(self, response: reqwest::Response) -> Result<()> { pub fn respond(self, response: impl Into<reqwest::Response>) -> Result<()> {
self.net._on(self.request, response, self.match_on) self.net._on(self.request, response.into(), self.match_on)
} }
} }

View file

@ -17,7 +17,7 @@ async fn test_get_url() {
.expect("request body"); .expect("request body");
net.on(request) net.on(request)
.respond(my_response.into()) .respond(my_response)
.expect("on request, respond"); .expect("on request, respond");
//when //when
@ -46,7 +46,7 @@ async fn test_get_wrong_url() {
.expect("request body"); .expect("request body");
net.on(request) net.on(request)
.respond(my_response.into()) .respond(my_response)
.expect("on request, respond"); .expect("on request, respond");
//when //when
@ -77,7 +77,7 @@ async fn test_post_url() {
.expect("request body"); .expect("request body");
net.on(request) net.on(request)
.respond(my_response.into()) .respond(my_response)
.expect("on request, respond"); .expect("on request, respond");
//when //when
@ -110,7 +110,7 @@ async fn test_post_by_method() {
MatchOn::Method, MatchOn::Method,
// MatchOn::Url // MatchOn::Url
]) ])
.respond(my_response.into()) .respond(my_response)
.expect("on request, respond"); .expect("on request, respond");
//when //when
@ -144,7 +144,7 @@ async fn test_post_by_url() {
// MatchOn::Method, // MatchOn::Method,
MatchOn::Url, MatchOn::Url,
]) ])
.respond(my_response.into()) .respond(my_response)
.expect("on request, respond"); .expect("on request, respond");
//when //when
@ -183,7 +183,7 @@ async fn test_post_by_body() {
// MatchOn::Url // MatchOn::Url
MatchOn::Body, MatchOn::Body,
]) ])
.respond(my_response.into()) .respond(my_response)
.expect("on request, respond"); .expect("on request, respond");
//when //when
@ -226,7 +226,7 @@ async fn test_post_by_headers() {
// MatchOn::Url // MatchOn::Url
MatchOn::Headers, MatchOn::Headers,
]) ])
.respond(my_response.into()) .respond(my_response)
.expect("on request, respond"); .expect("on request, respond");
//when //when
@ -265,7 +265,7 @@ async fn test_unused_post() {
.expect("request body"); .expect("request body");
net.on(request) net.on(request)
.respond(my_response.into()) .respond(my_response)
.expect("on request, respond"); .expect("on request, respond");
//when //when