Set up automated tests for the application Core #266
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue