feat: Net<Mocked> uses internal mutability
This commit is contained in:
parent
17c1b4ff6d
commit
69c1ac8565
3 changed files with 64 additions and 39 deletions
|
@ -1,5 +1,4 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
use derive_more::derive::From;
|
use derive_more::derive::From;
|
||||||
|
|
||||||
/// Represents a error accessing the network.
|
/// Represents a error accessing the network.
|
||||||
|
@ -9,14 +8,14 @@ pub enum Error {
|
||||||
Request(String),
|
Request(String),
|
||||||
#[display("Unexpected request: {0}", 0.to_string())]
|
#[display("Unexpected request: {0}", 0.to_string())]
|
||||||
UnexpectedMockRequest(reqwest::Request),
|
UnexpectedMockRequest(reqwest::Request),
|
||||||
|
RwLockLocked,
|
||||||
}
|
}
|
||||||
impl std::error::Error for Error {}
|
impl std::error::Error for Error {}
|
||||||
impl Clone for Error {
|
impl Clone for Error {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Reqwest(req) => Self::Request(req.to_string()),
|
Self::Reqwest(req) => Self::Request(req.to_string()),
|
||||||
Self::Request(req) => Self::Request(req.clone()),
|
err => err.clone(),
|
||||||
Self::UnexpectedMockRequest(_) => todo!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
use std::marker::PhantomData;
|
use std::{marker::PhantomData, sync::RwLock};
|
||||||
|
|
||||||
use super::{Error, Result};
|
use super::{Error, Result};
|
||||||
|
|
||||||
|
@ -27,19 +27,20 @@ pub struct Plan {
|
||||||
match_on: Vec<MatchOn>,
|
match_on: Vec<MatchOn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Net<T: NetType> {
|
pub struct Net<T: NetType> {
|
||||||
_type: PhantomData<T>,
|
_type: PhantomData<T>,
|
||||||
plans: Plans,
|
plans: RwLock<Plans>,
|
||||||
}
|
}
|
||||||
impl Net<Unmocked> {
|
impl Net<Unmocked> {
|
||||||
pub(crate) const fn new() -> Self {
|
pub(crate) const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
_type: PhantomData,
|
_type: PhantomData,
|
||||||
plans: vec![],
|
plans: RwLock::new(vec![]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send(&mut self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> {
|
pub async fn send(&self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> {
|
||||||
request.send().await.map_err(Error::from)
|
request.send().await.map_err(Error::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,12 +54,14 @@ impl Net<Mocked> {
|
||||||
pub(crate) const fn new() -> Self {
|
pub(crate) const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
_type: PhantomData,
|
_type: PhantomData,
|
||||||
plans: vec![],
|
plans: RwLock::new(vec![]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn send(&mut self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> {
|
|
||||||
|
pub async fn send(&self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> {
|
||||||
let request = request.build()?;
|
let request = request.build()?;
|
||||||
let index = self.plans.iter().position(|plan| {
|
let read_plans = self.plans.read().map_err(|_| Error::RwLockLocked)?;
|
||||||
|
let index = read_plans.iter().position(|plan| {
|
||||||
// METHOD
|
// METHOD
|
||||||
(if plan.match_on.contains(&MatchOn::Method) {
|
(if plan.match_on.contains(&MatchOn::Method) {
|
||||||
plan.request.method() == request.method()
|
plan.request.method() == request.method()
|
||||||
|
@ -88,18 +91,22 @@ impl Net<Mocked> {
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
drop(read_plans);
|
||||||
match index {
|
match index {
|
||||||
Some(i) => Ok(self.plans.remove(i).response),
|
Some(i) => {
|
||||||
|
let mut write_plans = self.plans.write().map_err(|_| Error::RwLockLocked)?;
|
||||||
|
Ok(write_plans.remove(i).response)
|
||||||
|
}
|
||||||
None => Err(Error::UnexpectedMockRequest(request)),
|
None => Err(Error::UnexpectedMockRequest(request)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [ResponseBuilder] to be extended and returned by a mocked network request.
|
/// Creates a [http::response::Builder] to be extended and returned by a mocked network request.
|
||||||
pub fn response(&self) -> http::response::Builder {
|
pub fn response(&self) -> http::response::Builder {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on(&mut self, request: reqwest::Request) -> OnRequest {
|
pub fn on(&self, request: reqwest::Request) -> OnRequest {
|
||||||
OnRequest {
|
OnRequest {
|
||||||
net: self,
|
net: self,
|
||||||
request,
|
request,
|
||||||
|
@ -108,30 +115,37 @@ impl Net<Mocked> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _on(
|
fn _on(
|
||||||
&mut self,
|
&self,
|
||||||
request: reqwest::Request,
|
request: reqwest::Request,
|
||||||
response: reqwest::Response,
|
response: reqwest::Response,
|
||||||
match_on: Vec<MatchOn>,
|
match_on: Vec<MatchOn>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
self.plans.push(Plan {
|
let mut write_plans = self.plans.write().map_err(|_| Error::RwLockLocked)?;
|
||||||
|
write_plans.push(Plan {
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
match_on,
|
match_on,
|
||||||
})
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&self) -> Result<()> {
|
||||||
self.plans = vec![];
|
let mut write_plans = self.plans.write().map_err(|_| Error::RwLockLocked)?;
|
||||||
|
write_plans.clear();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: NetType> Drop for Net<T> {
|
impl<T: NetType> Drop for Net<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
assert!(self.plans.is_empty())
|
let Ok(read_plans) = self.plans.read() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
assert!(read_plans.is_empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OnRequest<'net> {
|
pub struct OnRequest<'net> {
|
||||||
net: &'net mut Net<Mocked>,
|
net: &'net Net<Mocked>,
|
||||||
request: reqwest::Request,
|
request: reqwest::Request,
|
||||||
match_on: Vec<MatchOn>,
|
match_on: Vec<MatchOn>,
|
||||||
}
|
}
|
||||||
|
@ -143,7 +157,7 @@ impl<'net> OnRequest<'net> {
|
||||||
match_on,
|
match_on,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn respond(self, response: reqwest::Response) {
|
pub fn respond(self, response: reqwest::Response) -> Result<()> {
|
||||||
self.net._on(self.request, response, self.match_on)
|
self.net._on(self.request, response, self.match_on)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
46
tests/net.rs
46
tests/net.rs
|
@ -5,7 +5,7 @@ use kxio::net::{Error, MatchOn};
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_url() {
|
async fn test_get_url() {
|
||||||
//given
|
//given
|
||||||
let mut net = kxio::net::mock();
|
let net = kxio::net::mock();
|
||||||
let client = net.client();
|
let client = net.client();
|
||||||
|
|
||||||
let url = "https://www.example.com";
|
let url = "https://www.example.com";
|
||||||
|
@ -16,7 +16,9 @@ async fn test_get_url() {
|
||||||
.body("Get OK")
|
.body("Get OK")
|
||||||
.expect("request body");
|
.expect("request body");
|
||||||
|
|
||||||
net.on(request).respond(my_response.into());
|
net.on(request)
|
||||||
|
.respond(my_response.into())
|
||||||
|
.expect("on request, respond");
|
||||||
|
|
||||||
//when
|
//when
|
||||||
let response = net.send(client.get(url)).await.expect("response");
|
let response = net.send(client.get(url)).await.expect("response");
|
||||||
|
@ -29,7 +31,7 @@ async fn test_get_url() {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_wrong_url() {
|
async fn test_get_wrong_url() {
|
||||||
//given
|
//given
|
||||||
let mut net = kxio::net::mock();
|
let net = kxio::net::mock();
|
||||||
let client = net.client();
|
let client = net.client();
|
||||||
|
|
||||||
let url = "https://www.example.com";
|
let url = "https://www.example.com";
|
||||||
|
@ -40,7 +42,9 @@ async fn test_get_wrong_url() {
|
||||||
.body("Get OK")
|
.body("Get OK")
|
||||||
.expect("request body");
|
.expect("request body");
|
||||||
|
|
||||||
net.on(request).respond(my_response.into());
|
net.on(request)
|
||||||
|
.respond(my_response.into())
|
||||||
|
.expect("on request, respond");
|
||||||
|
|
||||||
//when
|
//when
|
||||||
let_assert!(
|
let_assert!(
|
||||||
|
@ -52,13 +56,13 @@ async fn test_get_wrong_url() {
|
||||||
assert_eq!(invalid_request.url().to_string(), "https://some.other.url/");
|
assert_eq!(invalid_request.url().to_string(), "https://some.other.url/");
|
||||||
|
|
||||||
// remove pending unmatched request - we never meant to match against it
|
// remove pending unmatched request - we never meant to match against it
|
||||||
net.reset();
|
net.reset().expect("reset");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_post_url() {
|
async fn test_post_url() {
|
||||||
//given
|
//given
|
||||||
let mut net = kxio::net::mock();
|
let net = kxio::net::mock();
|
||||||
let client = net.client();
|
let client = net.client();
|
||||||
|
|
||||||
let url = "https://www.example.com";
|
let url = "https://www.example.com";
|
||||||
|
@ -69,7 +73,9 @@ async fn test_post_url() {
|
||||||
.body("Post OK")
|
.body("Post OK")
|
||||||
.expect("request body");
|
.expect("request body");
|
||||||
|
|
||||||
net.on(request).respond(my_response.into());
|
net.on(request)
|
||||||
|
.respond(my_response.into())
|
||||||
|
.expect("on request, respond");
|
||||||
|
|
||||||
//when
|
//when
|
||||||
let response = net.send(client.post(url)).await.expect("reponse");
|
let response = net.send(client.post(url)).await.expect("reponse");
|
||||||
|
@ -82,7 +88,7 @@ async fn test_post_url() {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_post_by_method() {
|
async fn test_post_by_method() {
|
||||||
//given
|
//given
|
||||||
let mut net = kxio::net::mock();
|
let net = kxio::net::mock();
|
||||||
let client = net.client();
|
let client = net.client();
|
||||||
|
|
||||||
let url = "https://www.example.com";
|
let url = "https://www.example.com";
|
||||||
|
@ -98,7 +104,8 @@ async fn test_post_by_method() {
|
||||||
MatchOn::Method,
|
MatchOn::Method,
|
||||||
// MatchOn::Url
|
// MatchOn::Url
|
||||||
])
|
])
|
||||||
.respond(my_response.into());
|
.respond(my_response.into())
|
||||||
|
.expect("on request, respond");
|
||||||
|
|
||||||
//when
|
//when
|
||||||
// This request is a different url - but should still match
|
// This request is a different url - but should still match
|
||||||
|
@ -115,7 +122,7 @@ async fn test_post_by_method() {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_post_by_url() {
|
async fn test_post_by_url() {
|
||||||
//given
|
//given
|
||||||
let mut net = kxio::net::mock();
|
let net = kxio::net::mock();
|
||||||
let client = net.client();
|
let client = net.client();
|
||||||
|
|
||||||
let url = "https://www.example.com";
|
let url = "https://www.example.com";
|
||||||
|
@ -131,7 +138,8 @@ async fn test_post_by_url() {
|
||||||
// MatchOn::Method,
|
// MatchOn::Method,
|
||||||
MatchOn::Url,
|
MatchOn::Url,
|
||||||
])
|
])
|
||||||
.respond(my_response.into());
|
.respond(my_response.into())
|
||||||
|
.expect("on request, respond");
|
||||||
|
|
||||||
//when
|
//when
|
||||||
// This request is a GET, not POST - but should still match
|
// This request is a GET, not POST - but should still match
|
||||||
|
@ -145,7 +153,7 @@ async fn test_post_by_url() {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_post_by_body() {
|
async fn test_post_by_body() {
|
||||||
//given
|
//given
|
||||||
let mut net = kxio::net::mock();
|
let net = kxio::net::mock();
|
||||||
let client = net.client();
|
let client = net.client();
|
||||||
|
|
||||||
let url = "https://www.example.com";
|
let url = "https://www.example.com";
|
||||||
|
@ -166,7 +174,8 @@ async fn test_post_by_body() {
|
||||||
// MatchOn::Url
|
// MatchOn::Url
|
||||||
MatchOn::Body,
|
MatchOn::Body,
|
||||||
])
|
])
|
||||||
.respond(my_response.into());
|
.respond(my_response.into())
|
||||||
|
.expect("on request, respond");
|
||||||
|
|
||||||
//when
|
//when
|
||||||
// This request is a GET, not POST - but should still match
|
// This request is a GET, not POST - but should still match
|
||||||
|
@ -186,7 +195,7 @@ async fn test_post_by_body() {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_post_by_headers() {
|
async fn test_post_by_headers() {
|
||||||
//given
|
//given
|
||||||
let mut net = kxio::net::mock();
|
let net = kxio::net::mock();
|
||||||
let client = net.client();
|
let client = net.client();
|
||||||
|
|
||||||
let url = "https://www.example.com";
|
let url = "https://www.example.com";
|
||||||
|
@ -208,7 +217,8 @@ async fn test_post_by_headers() {
|
||||||
// MatchOn::Url
|
// MatchOn::Url
|
||||||
MatchOn::Headers,
|
MatchOn::Headers,
|
||||||
])
|
])
|
||||||
.respond(my_response.into());
|
.respond(my_response.into())
|
||||||
|
.expect("on request, respond");
|
||||||
|
|
||||||
//when
|
//when
|
||||||
// This request is a GET, not POST - but should still match
|
// This request is a GET, not POST - but should still match
|
||||||
|
@ -234,7 +244,7 @@ async fn test_post_by_headers() {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
async fn test_unused_post() {
|
async fn test_unused_post() {
|
||||||
//given
|
//given
|
||||||
let mut net = kxio::net::mock();
|
let net = kxio::net::mock();
|
||||||
let client = net.client();
|
let client = net.client();
|
||||||
|
|
||||||
let url = "https://www.example.com";
|
let url = "https://www.example.com";
|
||||||
|
@ -245,7 +255,9 @@ async fn test_unused_post() {
|
||||||
.body("Post OK")
|
.body("Post OK")
|
||||||
.expect("request body");
|
.expect("request body");
|
||||||
|
|
||||||
net.on(request).respond(my_response.into());
|
net.on(request)
|
||||||
|
.respond(my_response.into())
|
||||||
|
.expect("on request, respond");
|
||||||
|
|
||||||
//when
|
//when
|
||||||
// don't send the planned request
|
// don't send the planned request
|
||||||
|
|
Loading…
Reference in a new issue