Set up automated tests for the application Core #266
|
@ -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"
|
||||||
|
|
|
@ -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)]
|
||||||
|
@ -52,7 +53,7 @@ impl From<String> for AssetId {
|
||||||
|
|
||||||
pub struct AssetIter<'a>(Iter<'a, AssetId, String>);
|
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);
|
type Item = (&'a AssetId, &'a String);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
@ -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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue