From 71b114c9b209ae1f10a49b78edf9ea588410483f Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 24 Nov 2024 09:21:58 -0500 Subject: [PATCH] Set up some asset retrieval tests. --- visions/server/Cargo.toml | 4 +- visions/server/src/asset_db.rs | 40 +++++++++++++------- visions/server/src/core.rs | 67 +++++++++++++++++++++++++++------- visions/server/src/handlers.rs | 7 +--- 4 files changed, 85 insertions(+), 33 deletions(-) diff --git a/visions/server/Cargo.toml b/visions/server/Cargo.toml index 51dfaf0..0a185f8 100644 --- a/visions/server/Cargo.toml +++ b/visions/server/Cargo.toml @@ -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" diff --git a/visions/server/src/asset_db.rs b/visions/server/src/asset_db.rs index 6861dfa..23a7a4d 100644 --- a/visions/server/src/asset_db.rs +++ b/visions/server/src/asset_db.rs @@ -4,6 +4,7 @@ use std::{ io::Read, }; +use mime::Mime; use thiserror::Error; #[derive(Debug, Error)] @@ -52,7 +53,7 @@ impl From 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 { @@ -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, Error>; + fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec), Error>; } pub struct FsAssets { @@ -87,15 +88,16 @@ impl Assets for FsAssets { AssetIter(self.assets.iter()) } - fn get(&self, asset_id: AssetId) -> Result, Error> { + fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec), 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 = 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, + asset_paths: HashMap, + assets: HashMap>, } 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)>) -> 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, Error> { - unimplemented!() + fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec), 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), + } } } } diff --git a/visions/server/src/core.rs b/visions/server/src/core.rs index 4299603..aac0c15 100644 --- a/visions/server/src/core.rs +++ b/visions/server/src/core.rs @@ -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, 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), 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 { @@ -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()); + }); + } } diff --git a/visions/server/src/handlers.rs b/visions/server/src/handlers.rs index e58fc6b..bf94976 100644 --- a/visions/server/src/handlers.rs +++ b/visions/server/src/handlers.rs @@ -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()) })