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" tokio-stream = "0.1.16"
typeshare = "1.0.4" typeshare = "1.0.4"
urlencoding = "2.1.3" urlencoding = "2.1.3"
cool_asserts = "2.0.3"
thiserror = "2.0.3" thiserror = "2.0.3"
[dev-dependencies]
cool_asserts = "2.0.3"

View File

@ -4,6 +4,7 @@ use std::{
io::Read, io::Read,
}; };
use mime::Mime;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -63,7 +64,7 @@ impl <'a> Iterator for AssetIter<'a> {
pub trait Assets { pub trait Assets {
fn assets<'a>(&'a self) -> AssetIter<'a>; 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 { pub struct FsAssets {
@ -87,15 +88,16 @@ impl Assets for FsAssets {
AssetIter(self.assets.iter()) 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) { let path = match self.assets.get(&asset_id) {
Some(asset) => Ok(asset), Some(asset) => Ok(asset),
None => Err(Error::NotFound), None => Err(Error::NotFound),
}?; }?;
let mime = mime_guess::from_path(&path).first().unwrap();
let mut content: Vec<u8> = Vec::new(); let mut content: Vec<u8> = Vec::new();
let mut file = std::fs::File::open(&path)?; let mut file = std::fs::File::open(&path)?;
file.read_to_end(&mut content)?; file.read_to_end(&mut content)?;
Ok(content) Ok((mime, content))
} }
} }
@ -106,26 +108,38 @@ pub mod mocks {
use super::*; use super::*;
pub struct MemoryAssets { pub struct MemoryAssets {
assets: HashMap<AssetId, String>, asset_paths: HashMap<AssetId, String>,
assets: HashMap<AssetId, Vec<u8>>,
} }
impl MemoryAssets { impl MemoryAssets {
pub fn new(data: Vec<(AssetId, String)>) -> Self { pub fn new(data: Vec<(AssetId, String, Vec<u8>)>) -> Self {
let mut m = HashMap::new(); let mut asset_paths = HashMap::new();
data.into_iter().for_each(|(asset, path)| { let mut assets = HashMap::new();
m.insert(asset, path); 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 { impl Assets for MemoryAssets {
fn assets<'a>(&'a self) -> AssetIter<'a> { 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> { fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
unimplemented!() 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}, sync::{Arc, RwLock},
}; };
use mime::Mime;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use urlencoding::decode; use urlencoding::decode;
use uuid::Uuid; use uuid::Uuid;
@ -82,13 +83,16 @@ impl Core {
self.0.read().unwrap().tabletop.clone() self.0.read().unwrap().tabletop.clone()
} }
pub async fn get_asset(&self, asset_id: AssetId) -> Result<Vec<u8>, AppError> { 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| { self.0
match err { .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::NotFound => AppError::NotFound(format!("{}", asset_id)),
asset_db::Error::Inaccessible => AppError::Inaccessible(format!("{}", asset_id)), asset_db::Error::Inaccessible => AppError::Inaccessible(format!("{}", asset_id)),
asset_db::Error::UnexpectedError(err) => AppError::Inaccessible(format!("{}", err)), asset_db::Error::UnexpectedError(err) => AppError::Inaccessible(format!("{}", err)),
}
}) })
} }
@ -132,20 +136,55 @@ impl Core {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use cool_asserts::assert_matches;
use crate::asset_db::mocks::MemoryAssets; 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] #[tokio::test]
async fn it_lists_available_images() { async fn it_lists_available_images() {
let assets = MemoryAssets::new(vec![ let core = test_core();
(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 image_paths = core.available_images(); let image_paths = core.available_images();
assert_eq!(image_paths.len(), 2); 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 { pub async fn handle_file(core: Core, asset_id: AssetId) -> impl Reply {
handler(async move { handler(async move {
let mimetype = mime_guess::from_path(&format!("{}", asset_id)) let (mime, bytes) = core.get_asset(asset_id).await?;
.first()
.unwrap();
let bytes = core.get_asset(asset_id).await?;
Ok(Response::builder() Ok(Response::builder()
.header("application-type", mimetype.to_string()) .header("application-type", mime.to_string())
.body(bytes) .body(bytes)
.unwrap()) .unwrap())
}) })