shrupl/src/test_util/mock_client.rs

331 lines
9.1 KiB
Rust
Raw Normal View History

use std::{
cell::{RefCell, RefMut},
collections::{HashMap, hash_map::Entry},
};
use crate::{
Error, Result, error_response,
file::{self, FileTrait},
sharry::{AliasID, Client, FileID, ShareID, Uri, json},
};
use super::mock_ids::CheckID;
#[derive(Debug, Default)]
pub struct MockClient {
shares: RefCell<HashMap<String, MockShare>>,
}
#[derive(Debug, Default)]
struct MockShare {
files: HashMap<String, MockFile>,
}
#[derive(Debug, Default)]
struct MockFile {
size: u64,
offset: u64,
}
impl From<&file::Checked> for MockFile {
fn from(value: &file::Checked) -> Self {
Self {
size: value.get_size(),
offset: 0,
}
}
}
impl MockClient {
fn insert_share(&self, share_id: &ShareID, share: MockShare) -> Result<()> {
let mut shares = self.shares.borrow_mut();
let Entry::Vacant(entry) = shares.entry(share_id.to_string()) else {
return Err(error_response!("can't insert share {share_id:?}!"));
};
entry.insert(share);
Ok(())
}
fn insert_file(&self, share_id: &ShareID, file_id: &FileID, file: MockFile) -> 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!("can't insert file {file_id:?}!"));
};
entry.insert(file);
Ok(())
}
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!("can't find share {share_id:?}!"))?;
Ok(RefMut::map(shares, |shares| {
shares.get_mut(share_id).unwrap()
}))
}
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(share_id)?;
// check file exists
share
.files
.get(file_id)
.ok_or_else(|| error_response!("can't find file {file_id:?}!"))?;
Ok(RefMut::map(share, move |share| {
share.files.get_mut(file_id).unwrap()
}))
}
}
impl Client for MockClient {
fn share_create(
&self,
uri: &Uri,
alias_id: &AliasID,
_: json::NewShareRequest,
) -> Result<ShareID> {
(uri, alias_id).check()?;
let share_id = true.into();
self.insert_share(&share_id, MockShare::default())?;
Ok(share_id)
}
fn share_notify(&self, uri: &Uri, alias_id: &AliasID, share_id: &ShareID) -> crate::Result<()> {
(uri, alias_id).check()?;
share_id.check()?;
let _share = self.get_share(share_id)?;
Ok(())
}
fn file_create(
&self,
uri: &Uri,
alias_id: &AliasID,
share_id: &ShareID,
file: &file::Checked,
) -> Result<FileID> {
(uri, alias_id).check()?;
share_id.check()?;
let file_id = true.into();
self.insert_file(share_id, &file_id, file.into())?;
Ok(file_id)
}
fn file_patch(
&self,
uri: &Uri,
alias_id: &AliasID,
share_id: &ShareID,
chunk: &file::Chunk,
) -> Result<()> {
(uri, alias_id).check()?;
(share_id, chunk.get_file_id()).check()?;
let file = self.get_file(share_id, chunk.get_file_id())?;
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!"));
}
let mut file = file;
file.offset += chunk.get_length();
Ok(())
}
}
// technically redundant, but kept for refactoring purposes
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
fn check_tostr_eq<L, R>(left: L, right: R)
where
L: ExactSizeIterator,
L::Item: ToString,
R: ExactSizeIterator,
R::Item: ToString,
{
assert_eq!(left.len(), right.len());
let l_strings: HashSet<_> = left.map(|s| s.to_string()).collect();
let r_strings: HashSet<_> = right.map(|s| s.to_string()).collect();
assert_eq!(l_strings, r_strings);
}
fn add_share(client: &MockClient) -> ShareID {
let share_id = ShareID::from(true);
client
.insert_share(&share_id, MockShare::default())
.unwrap();
share_id
}
fn add_file(client: &MockClient, share_id: &ShareID) -> FileID {
let file_id = FileID::from(true);
client
.insert_file(share_id, &file_id, MockFile::default())
.unwrap();
file_id
}
#[test]
fn insert_share_works() {
let client = MockClient::default();
let share_ids: [_; 10] = std::array::from_fn(|_| ShareID::from(true));
for share_id in share_ids.as_ref() {
assert!(client.insert_share(share_id, MockShare::default()).is_ok());
}
check_tostr_eq(client.shares.borrow().keys(), share_ids.iter());
}
#[test]
fn insert_share_double_errors() {
let client = MockClient::default();
let share_ids: [_; 10] = std::array::from_fn(|_| add_share(&client));
for share_id in share_ids.as_ref() {
let res = client.insert_share(&share_id, MockShare::default());
assert!(res.is_err());
2025-07-15 14:54:27 +00:00
assert!(res.unwrap_err().response_contains("can't insert share"));
}
}
#[test]
fn get_share_works() {
let client = MockClient::default();
let share_ids: [_; 10] = std::array::from_fn(|_| add_share(&client));
for share_id in share_ids.as_ref() {
assert!(client.get_share(share_id).is_ok());
}
}
#[test]
fn get_share_nex_errors() {
let client = MockClient::default();
add_share(&client);
let share_ids_nex: [_; 10] = std::array::from_fn(|_| ShareID::from(true));
for share_id_nex in share_ids_nex.as_ref() {
let res = client.get_share(share_id_nex);
assert!(res.is_err());
2025-07-15 14:54:27 +00:00
assert!(res.unwrap_err().response_contains("can't find share"));
}
}
#[test]
fn insert_file_works() {
let client = MockClient::default();
let share_id = add_share(&client);
let file_ids: [_; 10] = std::array::from_fn(|_| FileID::from(true));
for file_id in file_ids.as_ref() {
assert!(
client
.insert_file(&share_id, file_id, MockFile::default())
.is_ok()
);
}
let shares = client.shares.borrow();
let share = shares.get(&share_id.to_string()).unwrap();
check_tostr_eq(share.files.keys(), file_ids.iter());
}
#[test]
fn insert_file_nex_share_errors() {
let client = MockClient::default();
add_share(&client);
let share_id_nex = ShareID::default();
let res = client.insert_file(&share_id_nex, &FileID::from(true), MockFile::default());
assert!(res.is_err());
2025-07-15 14:54:27 +00:00
assert!(res.unwrap_err().response_contains("can't find share"));
}
#[test]
fn insert_file_double_errors() {
let client = MockClient::default();
let share_id = add_share(&client);
let file_ids: [_; 10] = std::array::from_fn(|_| add_file(&client, &share_id));
for file_id in file_ids.as_ref() {
let res = client.insert_file(&share_id, &file_id, MockFile::default());
assert!(res.is_err());
2025-07-15 14:54:27 +00:00
assert!(res.unwrap_err().response_contains("can't insert file"));
}
}
#[test]
fn get_file_works() {
let client = MockClient::default();
let share_id = add_share(&client);
let file_ids: [_; 10] = std::array::from_fn(|_| add_file(&client, &share_id));
for file_id in file_ids.as_ref() {
assert!(client.get_file(&share_id, file_id).is_ok());
}
}
#[test]
fn get_file_nex_errors() {
let client = MockClient::default();
let share_id = add_share(&client);
add_file(&client, &share_id);
let file_ids_nex: [_; 10] = std::array::from_fn(|_| FileID::from(true));
for file_id_nex in file_ids_nex.as_ref() {
let share_id_nex = ShareID::from(true);
let res = client.get_file(&share_id_nex, file_id_nex);
assert!(res.is_err());
2025-07-15 14:54:27 +00:00
assert!(res.unwrap_err().response_contains("can't find share"));
let res = client.get_file(&share_id, file_id_nex);
assert!(res.is_err());
2025-07-15 14:54:27 +00:00
assert!(res.unwrap_err().response_contains("can't find file"));
}
}
}