feat: Add Debug, Clone, Default, PartialEq, Eq, Send, Sync to as many or our types as possible.
- adds tokio::sync as a dependency to provide an async Mutex for Clone of Net and MockNet ## ƒs - adds `Clone` to `DirItem` - adds `Default`, `PartialEq` and `Eq` to `FileSystem`, `PathMarker`, `FileMarker` and `DirMarker` - adds `Default` to `PathReal` - adds `Clone`, `Debug`, `Default`, `PartialEq` and `Eq` to `Reader` ## net - `MockNet::try_from` now returns a `Future`, so should be `await`ed - adds `Debug` to `Plan` - adds `Debug` and `Default` to `Net` - adds `Debug`, `Clone` and `Default` to `MockNet` - adds `Debug`, `Clone`, `PartialEq` and `Eq` to `MatchRequest` - adds `Debug` and `Clone` to `WhenRequest` WIP: mutation tests
This commit is contained in:
parent
d664090648
commit
2ddc79d826
12 changed files with 215 additions and 49 deletions
|
@ -24,6 +24,7 @@ path-clean = "1.0"
|
|||
reqwest = { version = "0.12", features = [ "json" ] }
|
||||
url = "2.5"
|
||||
tempfile = "3.10"
|
||||
tokio = { version = "1.41", features = [ "sync" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert2 = "0.3"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
use crate::fs::{DirItem, DirItemIterator, Result};
|
||||
|
||||
use super::{DirHandle, Error, FileHandle, PathHandle, PathMarker};
|
||||
use super::{DirHandle, Error, PathHandle, PathMarker};
|
||||
|
||||
impl DirHandle {
|
||||
/// Creates a new, empty directory at the path
|
||||
|
@ -93,19 +93,6 @@ impl DirHandle {
|
|||
std::fs::remove_dir_all(self.as_pathbuf()).map_err(Error::Io)
|
||||
}
|
||||
}
|
||||
impl TryFrom<PathHandle<PathMarker>> for FileHandle {
|
||||
type Error = crate::fs::Error;
|
||||
|
||||
fn try_from(path: PathHandle<PathMarker>) -> std::result::Result<Self, Self::Error> {
|
||||
match path.as_file() {
|
||||
Ok(Some(dir)) => Ok(dir.clone()),
|
||||
Ok(None) => Err(crate::fs::Error::NotADirectory {
|
||||
path: path.as_pathbuf(),
|
||||
}),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<PathHandle<PathMarker>> for DirHandle {
|
||||
type Error = crate::fs::Error;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
|||
use super::Error;
|
||||
|
||||
/// Represents an item in a directory
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum DirItem {
|
||||
File(PathBuf),
|
||||
Dir(PathBuf),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
use crate::fs::Result;
|
||||
|
||||
use super::{reader::Reader, Error, FileHandle};
|
||||
use super::{reader::Reader, Error, FileHandle, PathHandle, PathMarker};
|
||||
|
||||
impl FileHandle {
|
||||
/// Returns a [Reader] for the file.
|
||||
|
@ -99,3 +99,16 @@ impl FileHandle {
|
|||
std::fs::hard_link(self.as_pathbuf(), dest.as_pathbuf()).map_err(Error::Io)
|
||||
}
|
||||
}
|
||||
impl TryFrom<PathHandle<PathMarker>> for FileHandle {
|
||||
type Error = crate::fs::Error;
|
||||
|
||||
fn try_from(path: PathHandle<PathMarker>) -> std::result::Result<Self, Self::Error> {
|
||||
match path.as_file() {
|
||||
Ok(Some(dir)) => Ok(dir.clone()),
|
||||
Ok(None) => Err(crate::fs::Error::NotAFile {
|
||||
path: path.as_pathbuf(),
|
||||
}),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,3 +98,25 @@ pub fn new(base: impl Into<PathBuf>) -> FileSystem {
|
|||
pub fn temp() -> Result<temp::TempFileSystem> {
|
||||
temp::TempFileSystem::new()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn is_normal<T: Sized + Send + Sync + Unpin>() {}
|
||||
|
||||
#[test]
|
||||
fn normal_types() {
|
||||
is_normal::<FileSystem>();
|
||||
is_normal::<DirItem>();
|
||||
is_normal::<DirItemIterator>();
|
||||
is_normal::<Reader>();
|
||||
is_normal::<PathReal<PathMarker>>();
|
||||
is_normal::<DirMarker>();
|
||||
is_normal::<FileMarker>();
|
||||
is_normal::<PathMarker>();
|
||||
is_normal::<DirHandle>();
|
||||
is_normal::<FileHandle>();
|
||||
is_normal::<PathHandle<PathMarker>>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,24 +12,24 @@ use super::{DirHandle, FileHandle, PathHandle};
|
|||
pub trait PathType {}
|
||||
|
||||
/// Path marker for the type of [PathReal].
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct PathMarker;
|
||||
impl PathType for PathMarker {}
|
||||
|
||||
/// File marker for the type of [PathReal].
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct FileMarker;
|
||||
impl PathType for FileMarker {}
|
||||
|
||||
/// Dir marker for the type of [PathReal].
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct DirMarker;
|
||||
impl PathType for DirMarker {}
|
||||
|
||||
/// Represents a path in the filesystem.
|
||||
///
|
||||
/// It can be a simple path, or it can be a file or a directory.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PathReal<T: PathType> {
|
||||
base: PathBuf,
|
||||
path: PathBuf,
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::fs::Result;
|
|||
use super::Error;
|
||||
|
||||
/// A reader for a file.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Reader {
|
||||
contents: String,
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use super::{
|
|||
};
|
||||
|
||||
/// Represents to base of a section of a file system.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct FileSystem {
|
||||
base: PathBuf,
|
||||
}
|
||||
|
|
|
@ -134,10 +134,14 @@
|
|||
//!
|
||||
//! ```rust
|
||||
//! # use kxio::net;
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() -> net::Result<()> {
|
||||
//! # let mock_net = net::mock();
|
||||
//! # let net = net::Net::from(mock_net);
|
||||
//! let mock_net = kxio::net::MockNet::try_from(net).expect("recover mock");
|
||||
//! let mock_net = kxio::net::MockNet::try_from(net).await.expect("recover mock");
|
||||
//! mock_net.reset();
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ````
|
||||
//!
|
||||
//! ## Error Handling
|
||||
|
@ -167,5 +171,5 @@ pub const fn new() -> Net {
|
|||
|
||||
/// Creates a new `MockNet` for use in tests.
|
||||
pub fn mock() -> MockNet {
|
||||
Net::mock()
|
||||
Default::default()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
//
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use std::{cell::RefCell, ops::Deref, rc::Rc, sync::Arc};
|
||||
|
||||
use reqwest::{Body, Client};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::net::{Method, Request, RequestBuilder, Response, Url};
|
||||
|
||||
|
@ -29,6 +31,7 @@ pub enum MatchOn {
|
|||
/// A planned request and the response to return
|
||||
///
|
||||
/// Contains a list of the criteria that a request must meet before being considered a match.
|
||||
#[derive(Debug)]
|
||||
struct Plan {
|
||||
match_request: Vec<MatchRequest>,
|
||||
response: Response,
|
||||
|
@ -57,22 +60,15 @@ impl Plan {
|
|||
}
|
||||
|
||||
/// An abstraction for the network
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Net {
|
||||
plans: Option<Rc<RefCell<Plans>>>,
|
||||
plans: Option<Arc<Mutex<RefCell<Plans>>>>,
|
||||
}
|
||||
impl Net {
|
||||
/// Creates a new unmocked [Net] for creating real network requests.
|
||||
pub(super) const fn new() -> Self {
|
||||
Self { plans: None }
|
||||
}
|
||||
|
||||
/// Creats a new [MockNet] for use in tests.
|
||||
pub(super) const fn mock() -> MockNet {
|
||||
MockNet {
|
||||
plans: RefCell::new(vec![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Net {
|
||||
/// Helper to create a default [Client].
|
||||
|
@ -114,27 +110,28 @@ impl Net {
|
|||
};
|
||||
let request = request.into().build()?;
|
||||
let index = plans
|
||||
.lock()
|
||||
.await
|
||||
.deref()
|
||||
.borrow()
|
||||
.iter()
|
||||
.position(|plan| plan.matches(&request));
|
||||
match index {
|
||||
Some(i) => {
|
||||
let response = plans.borrow_mut().remove(i).response;
|
||||
let response = plans.lock().await.borrow_mut().remove(i).response;
|
||||
Ok(response)
|
||||
}
|
||||
None => Err(Error::UnexpectedMockRequest(request)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<Net> for MockNet {
|
||||
type Error = super::Error;
|
||||
|
||||
fn try_from(net: Net) -> std::result::Result<Self, Self::Error> {
|
||||
impl MockNet {
|
||||
pub async fn try_from(net: Net) -> std::result::Result<Self, super::Error> {
|
||||
match &net.plans {
|
||||
Some(plans) => Ok(MockNet {
|
||||
plans: RefCell::new(plans.take()),
|
||||
plans: Rc::new(RefCell::new(plans.lock().await.take())),
|
||||
}),
|
||||
None => Err(Self::Error::NetIsNotAMock),
|
||||
None => Err(super::Error::NetIsNotAMock),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +147,8 @@ impl TryFrom<Net> for MockNet {
|
|||
/// ```rust
|
||||
/// use kxio::net::{Method, Url};
|
||||
/// # use kxio::net::Result;
|
||||
/// # fn run() -> Result<()> {
|
||||
/// # #[tokio::main]
|
||||
/// # async fn run() -> Result<()> {
|
||||
/// let mock_net = kxio::net::mock();
|
||||
/// let client = mock_net.client();
|
||||
/// // define an expected requet, and the response that should be returned
|
||||
|
@ -162,13 +160,14 @@ impl TryFrom<Net> for MockNet {
|
|||
///
|
||||
/// // In some rare cases you don't want to assert that all expected requests were made.
|
||||
/// // You should recover the `MockNet` from the `Net` and `MockNet::reset` it.
|
||||
/// let mock_net = kxio::net::MockNet::try_from(net)?;
|
||||
/// let mock_net = kxio::net::MockNet::try_from(net).await?;
|
||||
/// mock_net.reset(); // only if explicitly needed
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MockNet {
|
||||
plans: RefCell<Plans>,
|
||||
plans: Rc<RefCell<Plans>>,
|
||||
}
|
||||
impl MockNet {
|
||||
/// Helper to create a default [Client].
|
||||
|
@ -222,10 +221,11 @@ impl MockNet {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use kxio::net::Result;
|
||||
/// # fn run() -> Result<()> {
|
||||
/// # #[tokio::main]
|
||||
/// # async fn run() -> Result<()> {
|
||||
/// # let mock_net = kxio::net::mock();
|
||||
/// # let net: kxio::net::Net = mock_net.into();
|
||||
/// let mock_net = kxio::net::MockNet::try_from(net)?;
|
||||
/// let mock_net = kxio::net::MockNet::try_from(net).await?;
|
||||
/// mock_net.reset(); // only if explicitly needed
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
|
@ -239,7 +239,7 @@ impl From<MockNet> for Net {
|
|||
Self {
|
||||
// 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
|
||||
plans: Some(Rc::new(RefCell::new(mock_net.plans.take()))),
|
||||
plans: Some(Arc::new(Mutex::new(RefCell::new(mock_net.plans.take())))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,11 +252,12 @@ impl Drop for MockNet {
|
|||
impl Drop for Net {
|
||||
fn drop(&mut self) {
|
||||
if let Some(plans) = &self.plans {
|
||||
assert!(plans.borrow().is_empty())
|
||||
assert!(plans.try_lock().expect("lock plans").take().is_empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MatchRequest {
|
||||
Method(Method),
|
||||
Url(Url),
|
||||
|
@ -264,6 +265,7 @@ pub enum MatchRequest {
|
|||
Body(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WhenRequest<'net> {
|
||||
net: &'net MockNet,
|
||||
match_on: Vec<MatchRequest>,
|
||||
|
@ -302,3 +304,19 @@ impl<'net> WhenRequest<'net> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn is_normal<T: Sized + Send + Sync + Unpin>() {}
|
||||
|
||||
#[test]
|
||||
fn normal_types() {
|
||||
is_normal::<Net>();
|
||||
// is_normal::<MockNet>(); // only used in test setup - no need to be Send or Sync
|
||||
is_normal::<MatchRequest>();
|
||||
is_normal::<Plan>();
|
||||
// is_normal::<WhenRequest>(); // only used in test setup - no need to be Send or Sync
|
||||
}
|
||||
}
|
||||
|
|
120
tests/fs.rs
120
tests/fs.rs
|
@ -5,6 +5,8 @@ use kxio::fs;
|
|||
type TestResult = Result<(), fs::Error>;
|
||||
|
||||
mod path {
|
||||
use fs::{PathHandle, PathMarker};
|
||||
|
||||
use super::*;
|
||||
|
||||
mod is_link {
|
||||
|
@ -418,6 +420,30 @@ mod path {
|
|||
assert_eq!(src_pathbuf, dst_pathbuf);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_file() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let file_path = fs.base().join("foo");
|
||||
let file = fs.file(&file_path);
|
||||
|
||||
let path: PathHandle<PathMarker> = file.into();
|
||||
|
||||
assert_eq!(path.as_pathbuf(), file_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_dir() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let dir_path = fs.base().join("foo");
|
||||
let dir = fs.dir(&dir_path);
|
||||
|
||||
let path: PathHandle<PathMarker> = dir.into();
|
||||
|
||||
assert_eq!(path.as_pathbuf(), dir_path);
|
||||
}
|
||||
}
|
||||
|
||||
mod file {
|
||||
|
@ -518,6 +544,53 @@ mod file {
|
|||
path.remove().expect("remove");
|
||||
}
|
||||
|
||||
mod from_path {
|
||||
use fs::{Error, FileHandle};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn path_is_dir() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
let pathbuf = fs.base().join("foo");
|
||||
let dir = fs.dir(&pathbuf);
|
||||
dir.create().expect("create dir");
|
||||
|
||||
let path = fs.path(&pathbuf);
|
||||
|
||||
let_assert!(Err(Error::NotAFile { path: err_path }) = FileHandle::try_from(path));
|
||||
|
||||
assert_eq!(err_path, pathbuf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_is_file() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
let pathbuf = fs.base().join("foo");
|
||||
let file = fs.file(&pathbuf);
|
||||
file.write("contents").expect("write file");
|
||||
|
||||
let path = fs.path(&pathbuf);
|
||||
|
||||
let_assert!(Ok(file_result) = FileHandle::try_from(path));
|
||||
|
||||
assert_eq!(file_result.as_pathbuf(), pathbuf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_is_error() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
let pathbuf = fs.base().join("foo");
|
||||
// does not exist
|
||||
|
||||
let path = fs.path(&pathbuf);
|
||||
|
||||
let_assert!(Err(Error::NotAFile { path: err_path }) = FileHandle::try_from(path));
|
||||
|
||||
assert_eq!(err_path, pathbuf);
|
||||
}
|
||||
}
|
||||
|
||||
mod remove {
|
||||
use super::*;
|
||||
#[test]
|
||||
|
@ -708,6 +781,53 @@ mod file {
|
|||
mod dir {
|
||||
use super::*;
|
||||
|
||||
mod from_path {
|
||||
use fs::{DirHandle, Error};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn path_is_dir() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
let pathbuf = fs.base().join("foo");
|
||||
let dir = fs.dir(&pathbuf);
|
||||
dir.create().expect("create dir");
|
||||
|
||||
let path = fs.path(&pathbuf);
|
||||
|
||||
let_assert!(Ok(dir_result) = DirHandle::try_from(path));
|
||||
|
||||
assert_eq!(dir_result.as_pathbuf(), pathbuf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_is_file() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
let pathbuf = fs.base().join("foo");
|
||||
let file = fs.file(&pathbuf);
|
||||
file.write("contents").expect("write file");
|
||||
|
||||
let path = fs.path(&pathbuf);
|
||||
|
||||
let_assert!(Err(Error::NotADirectory { path: err_path }) = DirHandle::try_from(path));
|
||||
|
||||
assert_eq!(err_path, pathbuf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_is_error() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
let pathbuf = fs.base().join("foo");
|
||||
// does not exist
|
||||
|
||||
let path = fs.path(&pathbuf);
|
||||
|
||||
let_assert!(Err(Error::NotADirectory { path: err_path }) = DirHandle::try_from(path));
|
||||
|
||||
assert_eq!(err_path, pathbuf);
|
||||
}
|
||||
}
|
||||
|
||||
mod create {
|
||||
use super::*;
|
||||
#[test]
|
||||
|
|
|
@ -62,7 +62,7 @@ async fn test_get_wrong_url() {
|
|||
assert_eq!(invalid_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).expect("recover net");
|
||||
let mock_net = MockNet::try_from(net).await.expect("recover net");
|
||||
mock_net.reset();
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ async fn test_post_by_header_wrong_value() {
|
|||
//then
|
||||
let_assert!(Err(kxio::net::Error::UnexpectedMockRequest(_)) = response);
|
||||
|
||||
MockNet::try_from(net).expect("recover mock").reset();
|
||||
MockNet::try_from(net).await.expect("recover mock").reset();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
Loading…
Reference in a new issue