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
7 changed files with 217 additions and 60 deletions
Showing only changes of commit 5d66558180 - Show all commits

98
Cargo.lock generated
View File

@ -258,7 +258,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -287,7 +287,7 @@ dependencies = [
"sha2",
"sqlformat",
"sqlx",
"thiserror",
"thiserror 1.0.64",
"tokio",
"uuid 0.4.0",
]
@ -368,7 +368,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -470,7 +470,7 @@ dependencies = [
"glib",
"libc",
"once_cell",
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -605,7 +605,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -652,7 +652,7 @@ dependencies = [
"cool_asserts",
"serde 1.0.210",
"serde_json",
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -712,7 +712,7 @@ version = "0.1.0"
dependencies = [
"nom",
"proptest",
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -955,7 +955,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -984,7 +984,7 @@ dependencies = [
"serde_derive",
"serde_json",
"tempfile",
"thiserror",
"thiserror 1.0.64",
"uuid 0.8.2",
]
@ -1130,7 +1130,7 @@ dependencies = [
"serde_json",
"sha2",
"tempdir",
"thiserror",
"thiserror 1.0.64",
"tokio",
"uuid 0.4.0",
"warp",
@ -1153,7 +1153,7 @@ dependencies = [
"glib-build-tools 0.18.0",
"gtk4",
"libadwaita",
"thiserror",
"thiserror 1.0.64",
"tokio",
]
@ -1230,7 +1230,7 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d"
dependencies = [
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -1373,7 +1373,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -1540,7 +1540,7 @@ dependencies = [
"once_cell",
"pin-project-lite",
"smallvec",
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -1576,7 +1576,7 @@ dependencies = [
"memchr",
"once_cell",
"smallvec",
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -1608,7 +1608,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -2120,7 +2120,7 @@ version = "0.1.0"
dependencies = [
"chrono",
"serde 1.0.210",
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -2801,7 +2801,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -2836,7 +2836,7 @@ dependencies = [
"serde 1.0.210",
"serde_json",
"sgf",
"thiserror",
"thiserror 1.0.64",
"uuid 0.8.2",
]
@ -3044,7 +3044,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -3084,7 +3084,7 @@ dependencies = [
"nix",
"once_cell",
"pipewire-sys",
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -3584,7 +3584,7 @@ dependencies = [
name = "result-extended"
version = "0.1.0"
dependencies = [
"thiserror",
"thiserror 1.0.64",
]
[[package]]
@ -3776,7 +3776,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -3836,7 +3836,7 @@ dependencies = [
"nary_tree",
"nom",
"serde 1.0.210",
"thiserror",
"thiserror 1.0.64",
"typeshare",
"uuid 0.8.2",
]
@ -4025,7 +4025,7 @@ dependencies = [
"sha2",
"smallvec",
"sqlformat",
"thiserror",
"thiserror 1.0.64",
"tokio",
"tokio-stream",
"tracing",
@ -4107,7 +4107,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"thiserror 1.0.64",
"tracing",
"whoami",
]
@ -4145,7 +4145,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"thiserror 1.0.64",
"tracing",
"whoami",
]
@ -4209,9 +4209,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.79"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
@ -4302,7 +4302,16 @@ version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.64",
]
[[package]]
name = "thiserror"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
dependencies = [
"thiserror-impl 2.0.3",
]
[[package]]
@ -4313,7 +4322,18 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
name = "thiserror-impl"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
@ -4438,7 +4458,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -4558,7 +4578,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -4600,7 +4620,7 @@ dependencies = [
"log 0.4.22",
"rand 0.8.5",
"sha1",
"thiserror",
"thiserror 1.0.64",
"url 2.5.2",
"utf-8",
]
@ -4654,7 +4674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f"
dependencies = [
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]
@ -4857,12 +4877,14 @@ name = "visions"
version = "0.1.0"
dependencies = [
"authdb",
"cool_asserts",
"futures",
"http 1.1.0",
"mime 0.3.17",
"mime_guess 2.0.5",
"serde 1.0.210",
"serde_json",
"thiserror 2.0.3",
"tokio",
"tokio-stream",
"typeshare",
@ -4958,7 +4980,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
"wasm-bindgen-shared",
]
@ -4992,7 +5014,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -5263,7 +5285,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.87",
]
[[package]]

View File

@ -19,3 +19,5 @@ 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"

View File

@ -0,0 +1,90 @@
use std::{
collections::HashMap,
fmt::{self, Display},
};
use thiserror::Error;
#[derive(Debug, Error)]
enum Error {
#[error("Asset could not be found: {0}")]
AssetNotFound(AssetId),
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct AssetId(String);
impl Display for AssetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AssetId({}", self.0)
}
}
impl From<&str> for AssetId {
fn from(s: &str) -> Self {
AssetId(s.to_owned())
}
}
pub trait Assets {
fn assets(&self) -> Vec<AssetId>;
fn get(&self, asset_id: AssetId) -> Result<Vec<u8>, Error>;
}
pub struct FsAssets {
assets: HashMap<AssetId, String>,
}
impl FsAssets {
pub fn new() -> Self {
Self {
assets: HashMap::new(),
}
}
fn assets<'a>(&'a self) -> impl Iterator<Item = &'a AssetId> {
self.assets.keys()
}
}
impl Assets for FsAssets {
fn assets(&self) -> Vec<AssetId> {
self.assets.keys().cloned().collect()
}
fn get(&self, asset_id: AssetId) -> Result<Vec<u8>, Error> {
unimplemented!()
}
}
#[cfg(test)]
pub mod mocks {
use std::collections::HashMap;
use super::*;
pub struct MemoryAssets {
assets: HashMap<AssetId, String>,
}
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);
});
Self { assets: m }
}
}
impl Assets for MemoryAssets {
fn assets(&self) -> Vec<AssetId> {
self.assets.keys().cloned().collect()
}
fn get(&self, asset_id: AssetId) -> Result<Vec<u8>, Error> {
unimplemented!()
}
}
}

View File

@ -9,32 +9,42 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use urlencoding::decode;
use uuid::Uuid;
use crate::types::{AppError, Message, Tabletop, RGB};
use crate::{
asset_db::{AssetId, Assets},
types::{AppError, Message, Tabletop, RGB},
};
#[derive(Debug)]
struct WebsocketClient {
sender: Option<UnboundedSender<Message>>,
}
#[derive(Debug)]
pub struct AppState {
pub image_base: PathBuf,
pub tabletop: Tabletop,
pub assets: Box<dyn Assets + Sync + Send + 'static>,
pub clients: HashMap<String, WebsocketClient>,
pub tabletop: Tabletop,
}
#[derive(Clone, Debug)]
pub struct Core(pub Arc<RwLock<AppState>>);
#[derive(Clone)]
pub struct Core(Arc<RwLock<AppState>>);
impl Core {
pub fn new() -> Self {
pub fn new<A>(assetdb: A) -> Self
where
A: Assets + Sync + Send + 'static,
{
Self(Arc::new(RwLock::new(AppState {
image_base: PathBuf::from("/home/savanni/Pictures"),
assets: Box::new(assetdb),
clients: HashMap::new(),
tabletop: Tabletop {
background_color: RGB{ red: 0xca, green: 0xb9, blue: 0xbb },
background_color: RGB {
red: 0xca,
green: 0xb9,
blue: 0xbb,
},
background_image: None,
},
clients: HashMap::new(),
})))
}
@ -68,21 +78,29 @@ impl Core {
}
}
pub fn get_file(&self, file_name: String) -> Vec<u8> {
pub fn tabletop(&self) -> Tabletop {
self.0.read().unwrap().tabletop.clone()
}
pub async fn get_file(&self, file_name: String) -> Vec<u8> {
/*
let file_name = decode(&file_name).expect("UTF-8");
let mut full_path = self.0.read().unwrap().image_base.clone();
full_path.push(file_name.to_string());
let mut content: Vec<u8> = Vec::new();
let mut file = std::fs::File::open(&full_path).unwrap();
file.read_to_end(&mut content).unwrap();
content
*/
unimplemented!()
}
pub fn available_images(&self) -> Vec<String> {
std::fs::read_dir(&self.0.read().unwrap().image_base)
pub fn available_images(&self) -> Vec<AssetId> {
self.0.read().unwrap().assets.assets()
/*
Ok(std::fs::read_dir(&self.0.read().unwrap().image_base)
.unwrap()
.filter_map(|entry| match entry {
Ok(entry_) => match mime_guess::from_path(entry_.path()).first() {
@ -98,7 +116,8 @@ impl Core {
},
Err(_) => None,
})
.collect()
.collect())
*/
}
pub fn set_background_image(&self, path: String) -> Result<(), AppError> {
@ -121,3 +140,24 @@ impl Core {
});
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::asset_db::mocks::MemoryAssets;
#[tokio::test]
async fn it_lists_available_images() {
let assets = MemoryAssets::new(vec![
(AssetId::from("asset_1"), "asset_1".to_owned()),
(AssetId::from("asset_2"), "asset_2".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();
assert_eq!(image_paths.len(), 5);
}
}

View File

@ -33,7 +33,7 @@ pub async fn handle_auth(
pub async fn handle_file(core: Core, file_name: String) -> impl Reply {
let mimetype = mime_guess::from_path(&file_name).first().unwrap();
let bytes = core.get_file(file_name);
let bytes = core.get_file(file_name).await;
Response::builder()
.header("application-type", mimetype.to_string())
.body(bytes)
@ -41,10 +41,13 @@ pub async fn handle_file(core: Core, file_name: String) -> impl Reply {
}
pub async fn handle_available_images(core: Core) -> impl Reply {
let image_paths: Vec<String> = core.available_images().into_iter()
.map(|path| format!("{}", path)).collect();
Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Content-Type", "application/json")
.body(serde_json::to_string(&core.available_images()).unwrap())
.body(serde_json::to_string(&image_paths).unwrap())
.unwrap()
}
@ -82,7 +85,6 @@ pub async fn handle_connect_websocket(
ws: warp::ws::Ws,
client_id: String,
) -> impl Reply {
// println!("handle_connect_websocket: {}", client_id);
ws.on_upgrade(move |socket| {
let core = core.clone();
async move {
@ -90,7 +92,7 @@ pub async fn handle_connect_websocket(
let mut receiver = core.connect_client(client_id.clone());
tokio::task::spawn(async move {
let tabletop = core.0.read().unwrap().tabletop.clone();
let tabletop = core.tabletop();
let _ = ws_sender
.send(Message::text(
serde_json::to_string(&crate::types::Message::UpdateTabletop(tabletop))

View File

@ -1,3 +1,4 @@
use asset_db::FsAssets;
use authdb::AuthError;
use handlers::{
handle_available_images, handle_connect_websocket, handle_file, handle_register_client, handle_set_background_image, handle_unregister_client, RegisterRequest
@ -13,10 +14,9 @@ use warp::{
Filter,
};
mod asset_db;
mod core;
mod handlers;
// use handlers::handle_auth;
mod types;
@ -96,7 +96,7 @@ async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible
#[tokio::main]
pub async fn main() {
let core = core::Core::new();
let core = core::Core::new(FsAssets::new());
let log = warp::log("visions::api");
let route_image = warp::path!("api" / "v1" / "image" / String)

View File

@ -3,6 +3,7 @@ use typeshare::typeshare;
#[derive(Debug)]
pub enum AppError {
NotFound(String),
JsonError(serde_json::Error),
}