[wip] unit testing

- mock impl for `sharry::Client` and associated IDs
This commit is contained in:
Jörn-Michael Miehe 2025-07-08 21:41:38 +00:00
parent 908e0031e2
commit 3257a97351
5 changed files with 123 additions and 65 deletions

35
Cargo.lock generated
View file

@ -79,6 +79,15 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "atomic"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
dependencies = [
"bytemuck",
]
[[package]]
name = "base64"
version = "0.22.1"
@ -108,6 +117,12 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
[[package]]
name = "bytes"
version = "1.10.1"
@ -874,6 +889,12 @@ dependencies = [
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
version = "1.0.20"
@ -944,6 +965,7 @@ dependencies = [
"tempfile",
"thiserror 2.0.12",
"ureq",
"uuid",
]
[[package]]
@ -1166,6 +1188,18 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
dependencies = [
"atomic",
"getrandom 0.3.3",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "version_check"
version = "0.9.5"
@ -1195,6 +1229,7 @@ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]

View file

@ -23,6 +23,7 @@ ureq = { version = "3.0.11", features = ["json"] }
[dev-dependencies]
tempfile = "3.20.0"
uuid = { version = "1.17.0", features = ["rng", "std", "v1"] }
[profile.release]
# Optimize for speed even more aggressively

View file

@ -154,3 +154,13 @@ impl Error {
}
}
}
#[macro_export]
macro_rules! error_response {
// Match a format string plus optional arguments
($fmt:expr $(, $arg:expr )* $(,)?) => {
// Expand to constructing the Error::Response variant,
// wrapping a `format!(...)` call
Error::Response(format!($fmt $(, $arg )*))
};
}

View file

@ -3,16 +3,19 @@ use std::{
collections::{HashMap, hash_map::Entry},
};
use uuid::Uuid;
use crate::{
Error, Result,
Error, Result, error_response,
file::{self, FileTrait},
sharry::{AliasID, Client, FileID, ShareID, Uri, json},
};
use super::mock_ids::CheckID;
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct MockClient {
use_uuid: bool,
shares: RefCell<HashMap<String, MockShare>>,
}
@ -23,19 +26,25 @@ struct MockShare {
#[derive(Debug)]
struct MockFile {
name: String,
size: u64,
offset: u64,
}
impl From<bool> for MockClient {
fn from(value: bool) -> Self {
Self {
use_uuid: value,
..Default::default()
}
}
}
impl MockClient {
fn insert_share(&self, share_id: &ShareID) -> Result<()> {
let mut shares = self.shares.borrow_mut();
let Entry::Vacant(entry) = shares.entry(share_id.to_string()) else {
return Err(Error::response(format_args!(
"Can't create share {share_id:?}!"
)));
return Err(error_response!("can't insert share {share_id:?}!"));
};
entry.insert(MockShare::default());
@ -43,58 +52,46 @@ impl MockClient {
Ok(())
}
fn insert_file(
&self,
share_id: &ShareID,
file_id: &FileID,
name: String,
size: u64,
) -> Result<()> {
let mut share = self.get_share_mut(share_id)?;
fn insert_file(&self, share_id: &ShareID, file_id: &FileID, size: u64) -> Result<()> {
let mut share = self.get_share(share_id)?;
let Entry::Vacant(entry) = share.files.entry(file_id.to_string()) else {
return Err(Error::response(format_args!(
"Can't create file {file_id:?}!"
)));
return Err(error_response!("can't insert file {file_id:?}!"));
};
entry.insert(MockFile {
name,
size,
offset: 0,
});
entry.insert(MockFile { size, offset: 0 });
Ok(())
}
fn get_share_mut<'t>(&'t self, share_id: &ShareID) -> Result<RefMut<'t, MockShare>> {
fn get_share<'t>(&'t self, share_id: &ShareID) -> Result<RefMut<'t, MockShare>> {
let share_id = &share_id.to_string();
let shares = self.shares.borrow_mut();
// check share exists
shares
.get(share_id)
.ok_or_else(|| Error::response(format_args!("Can't find share {share_id:?}!")))?;
.ok_or_else(|| error_response!("can't find share {share_id:?}!"))?;
// share exists
Ok(RefMut::map(shares, |shares| {
shares.get_mut(share_id).expect("checked but None!")
}))
}
fn get_file_mut<'t>(
fn get_file<'t>(
&'t self,
share_id: &ShareID,
file_id: &FileID,
) -> Result<RefMut<'t, MockFile>> {
let file_id = &file_id.to_string();
let share = self.get_share_mut(share_id)?;
let share = self.get_share(share_id)?;
// check file exists
share
.files
.get(file_id)
.ok_or_else(|| Error::response(format_args!("Can't find file {file_id:?}!")))?;
.ok_or_else(|| error_response!("can't find file {file_id:?}!"))?;
// file exists
Ok(RefMut::map(share, move |share| {
share.files.get_mut(file_id).expect("checked but None!")
}))
@ -110,7 +107,11 @@ impl Client for MockClient {
) -> Result<ShareID> {
(uri, alias_id).check()?;
let share_id = true.into();
let share_id = if self.use_uuid {
Uuid::now_v1(&[4, 8, 15, 16, 23, 42]).to_string().into()
} else {
true.into()
};
self.insert_share(&share_id)?;
Ok(share_id)
@ -120,6 +121,8 @@ impl Client for MockClient {
(uri, alias_id).check()?;
share_id.check()?;
let _share = self.get_share(share_id)?;
Ok(())
}
@ -133,13 +136,13 @@ impl Client for MockClient {
(uri, alias_id).check()?;
share_id.check()?;
let file_id = true.into();
self.insert_file(
share_id,
&file_id,
file.get_name().to_string(),
file.get_size(),
)?;
let file_id = if self.use_uuid {
let id = Uuid::now_v1(&[4, 8, 15, 16, 23, 42]);
FileID::new_test(id)
} else {
true.into()
};
self.insert_file(share_id, &file_id, file.get_size())?;
Ok(file_id)
}
@ -155,10 +158,21 @@ impl Client for MockClient {
(share_id, chunk.get_file_id()).check()?;
// TODO: `chunk` must align to a full MiB
let file = self.get_file_mut(share_id, chunk.get_file_id())?;
let file = self.get_file(share_id, chunk.get_file_id())?;
todo!()
if chunk.get_length() == 0 {
return Err(error_response!("chunk {chunk:?} empty!"));
} else if chunk.get_offset() % (1024 * 1024) != 0 {
return Err(error_response!("chunk {chunk:?} not aligned to a MiB!"));
} else if chunk.get_offset() != file.offset {
return Error::mismatch(file.offset, chunk.get_offset());
} else if file.offset + chunk.get_length() > file.size {
return Err(error_response!("chunk {chunk:?} too long!"));
}
// Ok(())
let mut file = file;
file.offset += chunk.get_length();
Ok(())
}
}

View file

@ -124,43 +124,41 @@ mod tests {
#[test]
fn false_makes_invalids() {
fn test_check(value: impl CheckID, callback: impl FnOnce(&Parameter) -> bool) {
let check = value.check().expect_err("should be invalid");
let p = check.get_invalid_param().expect("should be InvalidParam");
assert!(callback(p));
}
// valid ids
let uri = Uri::from(true);
let alias_id = AliasID::from(true);
let share_id = ShareID::from(true);
let file_id = FileID::from(true);
// invalid ids
let uri_i = Uri::from(false);
let alias_id_i = AliasID::from(false);
let share_id_i = ShareID::from(false);
let file_id_i = FileID::from(false);
// invalid Uri, valid alias
let check = (&uri_i, &alias_id).check().expect_err("should be invalid");
let p = check.get_invalid_param().expect("should be InvalidParam");
assert!(matches!(p, Parameter::Uri(_)));
// param checks
let is_uri_i = |p: &Parameter| matches!(p, Parameter::Uri(_));
let is_alias_id_i = |p: &Parameter| matches!(p, Parameter::AliasID(_));
let is_share_id_i = |p: &Parameter| matches!(p, Parameter::ShareID(_));
let is_file_id_i = |p: &Parameter| matches!(p, Parameter::FileID(_));
// valid Uri, invalid alias
let check = (&uri, &alias_id_i).check().expect_err("should be invalid");
let p = check.get_invalid_param().expect("should be InvalidParam");
assert!(matches!(p, Parameter::AliasID(_)));
// uri + alias
test_check((&uri_i, &alias_id_i), is_uri_i);
test_check((&uri_i, &alias_id), is_uri_i);
test_check((&uri, &alias_id_i), is_alias_id_i);
// invalid share
let check = share_id_i.check().expect_err("should be invalid");
let p = check.get_invalid_param().expect("should be InvalidParam");
assert!(matches!(p, Parameter::ShareID(_)));
// share
test_check(&share_id_i, is_share_id_i);
// invalid share, valid file
let check = (&share_id_i, &file_id)
.check()
.expect_err("should be invalid");
let p = check.get_invalid_param().expect("should be InvalidParam");
assert!(matches!(p, Parameter::ShareID(_)));
// valid share, invalid file
let check = (&share_id, &file_id_i)
.check()
.expect_err("should be invalid");
let p = check.get_invalid_param().expect("should be InvalidParam");
assert!(matches!(p, Parameter::FileID(_)));
// share + file
test_check((&share_id_i, &file_id_i), is_share_id_i);
test_check((&share_id_i, &file_id), is_share_id_i);
test_check((&share_id, &file_id_i), is_file_id_i);
}
}