diff --git a/Cargo.lock b/Cargo.lock index 4a5d4fb..470ed1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,18 @@ dependencies = [ "libc", ] +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + [[package]] name = "async-stream" version = "0.3.5" @@ -447,6 +459,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "either" version = "1.9.0" @@ -540,6 +558,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -620,6 +644,12 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.29" @@ -1136,6 +1166,33 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mockall" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "multer" version = "2.1.0" @@ -1496,6 +1553,32 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.70" @@ -1660,6 +1743,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + [[package]] name = "reqwest" version = "0.11.22" @@ -1805,6 +1894,35 @@ dependencies = [ "serde", ] +[[package]] +name = "rstest" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.39", + "unicode-ident", +] + [[package]] name = "rust-ini" version = "0.18.0" @@ -1853,6 +1971,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.25" @@ -1931,6 +2058,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "serde" version = "1.0.193" @@ -1987,14 +2120,20 @@ dependencies = [ name = "serves3" version = "1.0.0" dependencies = [ + "anyhow", + "async-trait", + "bytes", "config", "human-size", "lazy_static", + "mockall", "rocket", "rocket_dyn_templates", + "rstest", "rust-s3", "serde", "tempfile", + "tokio", ] [[package]] @@ -2185,6 +2324,12 @@ dependencies = [ "unic-segment", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" version = "1.0.50" @@ -2261,9 +2406,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", diff --git a/Cargo.toml b/Cargo.toml index fade79b..72b0635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,11 @@ rocket_dyn_templates = { version = "0.1.0", features = ["tera"] } rust-s3 = { version = "0.33", default-features = false, features = ["tokio-native-tls"] } serde = { version = "1.0" } tempfile = { version = "3.6" } + +[dev-dependencies] +anyhow = "1.0" +async-trait = "0.1" +bytes = "1.5" +mockall = "0.12" +rstest = "0.19" +tokio = "1.37" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b8ecbdf..c9174e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: EUPL-1.2 use { - lazy_static::lazy_static, rocket::response::Responder, rocket::serde::Serialize, rocket_dyn_templates::{context, Template}, std::path::PathBuf, }; +use lazy_static::lazy_static; + struct Settings { access_key_id: String, secret_access_key: String, @@ -67,7 +68,7 @@ lazy_static! { .expect("Unable to create an empty temporary folder, is the whole FS read-only?"); } -#[derive(Responder)] +#[derive(Responder, Debug)] enum FileView { #[response(content_type = "text/html")] Folder(Template), @@ -78,6 +79,7 @@ enum FileView { #[derive(Serialize)] struct FileViewItem { + parent: String, path: String, size: String, size_bytes: u64, @@ -159,7 +161,7 @@ async fn s3_fileview(path: &PathBuf) -> Result, Error> { }; let s3_objects = BUCKET - .list(s3_folder_path, Some("/".into())) + .list(s3_folder_path.clone(), Some("/".into())) .await .map_err(|_| Error::NotFound("Object not found".into()))?; @@ -175,6 +177,7 @@ async fn s3_fileview(path: &PathBuf) -> Result, Error> { let folders = list.common_prefixes.iter().flatten().map(|dir| { let path = dir.prefix.strip_prefix(&prefix); path.map(|path| FileViewItem { + parent: s3_folder_path.clone(), path: path.to_owned(), size_bytes: 0, size: "[DIR]".to_owned(), @@ -185,6 +188,7 @@ async fn s3_fileview(path: &PathBuf) -> Result, Error> { let files = list.contents.iter().map(|obj| { let path = obj.key.strip_prefix(&prefix); path.map(|path| FileViewItem { + parent: s3_folder_path.clone(), path: path.to_owned(), size_bytes: obj.size, size: size_bytes_to_human(obj.size), @@ -238,3 +242,82 @@ fn rocket() -> _ { .unwrap() })) } + +// Test section starts + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use anyhow::Result; + use async_trait::async_trait; + use bytes::Bytes; + + use rstest::rstest; + // Note this useful idiom: importing names from outer (for mod tests) scope. + use super::*; + use mockall::mock; + use s3::{error::S3Error, request::ResponseData}; + + /// A trait implemented by a Struct we want to mock + #[async_trait] + pub trait Bah { + async fn get_object + 'static + std::marker::Send>( + &self, + path: S, + ) -> Result; + } + + mock! { + pub Bucket {} + + #[async_trait] + impl Bah for Bucket { + async fn get_object + 'static + std::marker::Send>(&self, path: S) -> Result; + } + } + + #[rstest] + #[case(1024, "1.024 kB")] + #[case(10240, "10.240 kB")] + #[case(1024*1024, "1.049 MB")] + #[case(1024*1024*1024, "1.074 GB")] + #[case(0, "0.000 B")] + #[case(u64::MAX, format!("{:.3} GB",u64::MAX as f64/(1_000_000_000.0)))] + #[case(u64::MIN, format!("{:.3} B",u64::MIN as f64))] + + fn test_size_bytes_to_human(#[case] bytes: u64, #[case] expected: String) { + println!("{}", size_bytes_to_human(bytes)); + assert_eq!(size_bytes_to_human(bytes), expected); + } + + #[tokio::test] + async fn test_s3_serve_file() -> Result<()>{ + let mut mock_bucket = MockBucket::new(); + + mock_bucket.expect_get_object().times(1).returning(|_:String| { + panic!("here"); + // Ok(ResponseData::new( + // Bytes::new(), + // 200, + // HashMap::::new(), + // )) + }); + + let test_path: PathBuf = "foo".into(); + + let result = s3_serve_file(&test_path).await; + + println!("{:?}", result); + assert!(result.is_ok()); + let result = result.unwrap(); + + let bytes = match result { + FileView::File(b) => b, + _ => panic!("Should be a file."), + }; + + assert_eq!(bytes,vec![0]); + + Ok(()) + } +} \ No newline at end of file