From 3257a97351879433d5dda68e419ef6d19ae97b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:41:38 +0000 Subject: [PATCH] [wip] unit testing - mock impl for `sharry::Client` and associated IDs --- Cargo.lock | 35 ++++++++++++++ Cargo.toml | 1 + src/error.rs | 10 ++++ src/test_util/mock_client.rs | 94 +++++++++++++++++++++--------------- src/test_util/mock_ids.rs | 48 +++++++++--------- 5 files changed, 123 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22a2c88..855d7a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index aa0f373..0fbc4f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/src/error.rs b/src/error.rs index 9199f38..db047d3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 )*)) + }; +} diff --git a/src/test_util/mock_client.rs b/src/test_util/mock_client.rs index 626833f..4fed431 100644 --- a/src/test_util/mock_client.rs +++ b/src/test_util/mock_client.rs @@ -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>, } @@ -23,19 +26,25 @@ struct MockShare { #[derive(Debug)] struct MockFile { - name: String, size: u64, offset: u64, } +impl From 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> { + fn get_share<'t>(&'t self, share_id: &ShareID) -> Result> { 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> { 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 { (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(()) } } diff --git a/src/test_util/mock_ids.rs b/src/test_util/mock_ids.rs index 5618fdd..3c1ec09 100644 --- a/src/test_util/mock_ids.rs +++ b/src/test_util/mock_ids.rs @@ -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); } }