Set up automated tests for the application Core #266

Merged
savanni merged 6 commits from visions-core-test into main 2024-11-24 14:53:18 +00:00
4 changed files with 85 additions and 33 deletions
Showing only changes of commit 71b114c9b2 - Show all commits

View File

@ -19,5 +19,7 @@ futures = "0.3.31"
tokio-stream = "0.1.16"
typeshare = "1.0.4"
urlencoding = "2.1.3"
cool_asserts = "2.0.3"
thiserror = "2.0.3"
[dev-dependencies]
cool_asserts = "2.0.3"

View File

@ -4,6 +4,7 @@ use std::{
io::Read,
};
use mime::Mime;
use thiserror::Error;
#[derive(Debug, Error)]
@ -52,7 +53,7 @@ impl From<String> for AssetId {
pub struct AssetIter<'a>(Iter<'a, AssetId, String>);
impl <'a> Iterator for AssetIter<'a> {
impl<'a> Iterator for AssetIter<'a> {
type Item = (&'a AssetId, &'a String);
fn next(&mut self) -> Option<Self::Item> {
@ -63,7 +64,7 @@ impl <'a> Iterator for AssetIter<'a> {
pub trait Assets {
fn assets<'a>(&'a self) -> AssetIter<'a>;
fn get(&self, asset_id: AssetId) -> Result<Vec<u8>, Error>;
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error>;
}
pub struct FsAssets {
@ -87,15 +88,16 @@ impl Assets for FsAssets {
AssetIter(self.assets.iter())
}
fn get(&self, asset_id: AssetId) -> Result<Vec<u8>, Error> {
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
let path = match self.assets.get(&asset_id) {
Some(asset) => Ok(asset),
None => Err(Error::NotFound),
}?;
let mime = mime_guess::from_path(&path).first().unwrap();
let mut content: Vec<u8> = Vec::new();
let mut file = std::fs::File::open(&path)?;
file.read_to_end(&mut content)?;
Ok(content)
Ok((mime, content))
}
}
@ -106,26 +108,38 @@ pub mod mocks {
use super::*;
pub struct MemoryAssets {
assets: HashMap<AssetId, String>,
asset_paths: HashMap<AssetId, String>,
assets: HashMap<AssetId, Vec<u8>>,
}
impl MemoryAssets {
pub fn new(data: Vec<(AssetId, String)>) -> Self {
let mut m = HashMap::new();
data.into_iter().for_each(|(asset, path)| {
m.insert(asset, path);
pub fn new(data: Vec<(AssetId, String, Vec<u8>)>) -> Self {
let mut asset_paths = HashMap::new();
let mut assets = HashMap::new();
data.into_iter().for_each(|(asset, path, data)| {
asset_paths.insert(asset.clone(), path);
assets.insert(asset, data);
});
Self { assets: m }
Self {
asset_paths,
assets,
}
}
}
impl Assets for MemoryAssets {
fn assets<'a>(&'a self) -> AssetIter<'a> {
AssetIter(self.assets.iter())
AssetIter(self.asset_paths.iter())
}
fn get(&self, asset_id: AssetId) -> Result<Vec<u8>, Error> {
unimplemented!()
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
match (self.asset_paths.get(&asset_id), self.assets.get(&asset_id)) {
(Some(path), Some(data)) => {
let mime = mime_guess::from_path(&path).first().unwrap();
Ok((mime, data.to_vec()))
}
_ => Err(Error::NotFound),
}
}
}
}

View File

@ -5,6 +5,7 @@ use std::{
sync::{Arc, RwLock},
};
use mime::Mime;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use urlencoding::decode;
use uuid::Uuid;
@ -82,14 +83,17 @@ impl Core {
self.0.read().unwrap().tabletop.clone()
}
pub async fn get_asset(&self, asset_id: AssetId) -> Result<Vec<u8>, AppError> {
self.0.read().unwrap().asset_db.get(asset_id.clone()).map_err(|err| {
match err {
pub async fn get_asset(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), AppError> {
self.0
.read()
.unwrap()
.asset_db
.get(asset_id.clone())
.map_err(|err| match err {
asset_db::Error::NotFound => AppError::NotFound(format!("{}", asset_id)),
asset_db::Error::Inaccessible => AppError::Inaccessible(format!("{}", asset_id)),
asset_db::Error::UnexpectedError(err) => AppError::Inaccessible(format!("{}", err)),
}
})
})
}
pub fn available_images(&self) -> Vec<AssetId> {
@ -132,20 +136,55 @@ impl Core {
#[cfg(test)]
mod test {
use super::*;
use cool_asserts::assert_matches;
use crate::asset_db::mocks::MemoryAssets;
fn test_core() -> Core {
let assets = MemoryAssets::new(vec![
(
AssetId::from("asset_1"),
"asset_1.png".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_2"),
"asset_2.jpg".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_3"),
"asset_3".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_4"),
"asset_4".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_5"),
"asset_5".to_owned(),
String::from("abcdefg").into_bytes(),
),
]);
Core::new(assets)
}
#[tokio::test]
async fn it_lists_available_images() {
let assets = MemoryAssets::new(vec![
(AssetId::from("asset_1"), "asset_1.png".to_owned()),
(AssetId::from("asset_2"), "asset_2.jpg".to_owned()),
(AssetId::from("asset_3"), "asset_3".to_owned()),
(AssetId::from("asset_4"), "asset_4".to_owned()),
(AssetId::from("asset_5"), "asset_5".to_owned()),
]);
let core = Core::new(assets);
let core = test_core();
let image_paths = core.available_images();
assert_eq!(image_paths.len(), 2);
}
#[tokio::test]
async fn it_retrieves_an_asset() {
let core = test_core();
assert_matches!(core.get_asset(AssetId::from("asset_1")).await, Ok((mime, data)) => {
assert_eq!(mime.type_(), mime::IMAGE);
assert_eq!(data, "abcdefg".as_bytes());
});
}
}

View File

@ -49,12 +49,9 @@ where
pub async fn handle_file(core: Core, asset_id: AssetId) -> impl Reply {
handler(async move {
let mimetype = mime_guess::from_path(&format!("{}", asset_id))
.first()
.unwrap();
let bytes = core.get_asset(asset_id).await?;
let (mime, bytes) = core.get_asset(asset_id).await?;
Ok(Response::builder()
.header("application-type", mimetype.to_string())
.header("application-type", mime.to_string())
.body(bytes)
.unwrap())
})