From 532cb50773e32f079a172b5fc928a007ce852416 Mon Sep 17 00:00:00 2001
From: Eduardo Trujillo <ed@chromabits.com>
Date: Tue, 15 Nov 2022 18:20:00 -0800
Subject: [PATCH] test(files): Enable tests in files service again

---
 Cargo.lock       |  101 ++++
 Cargo.toml       |    3 +
 src/files/mod.rs | 1360 ++++++++++++++++++----------------------------
 3 files changed, 624 insertions(+), 840 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index a47aacf..4142925 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -181,6 +181,32 @@ dependencies = [
  "zstd",
 ]
 
+[[package]]
+name = "actix-http-test"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40511826540d084fbcd68ee65b75b1849961c1760a193b09180a4851f20075b"
+dependencies = [
+ "actix-codec 0.5.0",
+ "actix-rt 2.7.0",
+ "actix-server",
+ "actix-service 2.0.2",
+ "actix-tls",
+ "actix-utils 3.0.1",
+ "awc",
+ "base64 0.13.1",
+ "bytes 1.2.1",
+ "futures-core",
+ "http",
+ "log",
+ "serde",
+ "serde_json",
+ "serde_urlencoded 0.7.1",
+ "slab",
+ "socket2 0.4.7",
+ "tokio 1.21.2",
+]
+
 [[package]]
 name = "actix-macros"
 version = "0.1.3"
@@ -279,6 +305,29 @@ dependencies = [
  "pin-project-lite 0.2.9",
 ]
 
+[[package]]
+name = "actix-test"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "546b075f2ee13e081a040b60b95a08f0eceaac6bc759309026611234dc80abfe"
+dependencies = [
+ "actix-codec 0.5.0",
+ "actix-http 3.2.2",
+ "actix-http-test",
+ "actix-rt 2.7.0",
+ "actix-service 2.0.2",
+ "actix-utils 3.0.1",
+ "actix-web",
+ "awc",
+ "futures-core",
+ "futures-util",
+ "log",
+ "serde",
+ "serde_json",
+ "serde_urlencoded 0.7.1",
+ "tokio 1.21.2",
+]
+
 [[package]]
 name = "actix-threadpool"
 version = "0.3.3"
@@ -294,6 +343,23 @@ dependencies = [
  "threadpool",
 ]
 
+[[package]]
+name = "actix-tls"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297"
+dependencies = [
+ "actix-codec 0.5.0",
+ "actix-rt 2.7.0",
+ "actix-service 2.0.2",
+ "actix-utils 3.0.1",
+ "futures-core",
+ "http",
+ "log",
+ "pin-project-lite 0.2.9",
+ "tokio-util 0.7.4",
+]
+
 [[package]]
 name = "actix-utils"
 version = "1.0.6"
@@ -539,6 +605,40 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "awc"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80ca7ff88063086d2e2c70b9f3b29b2fcd999bac68ac21731e66781970d68519"
+dependencies = [
+ "actix-codec 0.5.0",
+ "actix-http 3.2.2",
+ "actix-rt 2.7.0",
+ "actix-service 2.0.2",
+ "actix-tls",
+ "actix-utils 3.0.1",
+ "ahash",
+ "base64 0.13.1",
+ "bytes 1.2.1",
+ "cfg-if 1.0.0",
+ "cookie",
+ "derive_more",
+ "futures-core",
+ "futures-util",
+ "h2 0.3.15",
+ "http",
+ "itoa 1.0.4",
+ "log",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite 0.2.9",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "serde_urlencoded 0.7.1",
+ "tokio 1.21.2",
+]
+
 [[package]]
 name = "axum"
 version = "0.5.17"
@@ -1101,6 +1201,7 @@ dependencies = [
  "actix-http 1.0.1",
  "actix-rt 2.7.0",
  "actix-service 2.0.2",
+ "actix-test",
  "actix-web",
  "anyhow",
  "async-compression",
diff --git a/Cargo.toml b/Cargo.toml
index b79716b..4657a66 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,5 +62,8 @@ rev = "1bd30fbd1a219e8982571da48eb68f34317d1e15"
 version = "0.3"
 features = ["gzip", "tokio"]
 
+[dev-dependencies]
+actix-test = "0.1.0"
+
 [features]
 console-subscriber = ["dep:console-subscriber", "tokio/tracing"]
\ No newline at end of file
diff --git a/src/files/mod.rs b/src/files/mod.rs
index 78e48b6..026d17d 100644
--- a/src/files/mod.rs
+++ b/src/files/mod.rs
@@ -69,7 +69,8 @@ pub struct Files {
   path_contexts: Arc<Vec<PathContext>>,
   redirect_to_slash: bool,
   default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
-  guards: Option<Rc<dyn Guard>>,
+  use_guards: Option<Rc<dyn Guard>>,
+  guards: Vec<Rc<dyn Guard>>,
 }
 
 impl Clone for Files {
@@ -79,9 +80,10 @@ impl Clone for Files {
       redirect_to_slash: self.redirect_to_slash,
       default: self.default.clone(),
       path: self.path.clone(),
-      guards: self.guards.clone(),
+      use_guards: self.use_guards.clone(),
       root_path_context: self.root_path_context.clone(),
       path_contexts: self.path_contexts.clone(),
+      guards: self.guards.clone(),
     }
   }
 }
@@ -109,11 +111,12 @@ impl Files {
     // };
 
     Files {
-      path: path.to_string(),
+      path: path.trim_end_matches('/').to_string(),
       directory,
       redirect_to_slash: false,
       default: Rc::new(RefCell::new(None)),
-      guards: None,
+      use_guards: None,
+      guards: vec![],
       root_path_context,
       path_contexts,
     }
@@ -127,12 +130,50 @@ impl Files {
     self
   }
 
+  /// Adds a routing guard.
+  ///
+  /// Use this to allow multiple chained file services that respond to strictly different
+  /// properties of a request. Due to the way routing works, if a guard check returns true and the
+  /// request starts being handled by the file service, it will not be able to back-out and try
+  /// the next service, you will simply get a 404 (or 405) error response.
+  ///
+  /// To allow `POST` requests to retrieve files, see [`Files::use_guards`].
+  ///
+  /// # Examples
+  /// ```
+  /// use std::{
+  ///   convert::TryInto,
+  ///   sync::Arc,
+  /// };
+  /// use actix_web::{guard::Header, App};
+  /// use espresso::{
+  ///   files::Files,
+  ///   config::ServerConfig,
+  /// };
+  /// use tokio::sync::RwLock;
+  ///
+  /// let mut server_config = ServerConfig::default();
+  ///
+  /// let serve_dir = Arc::new(RwLock::new(Some(".".into())));
+  /// let root_path_context = Arc::new((&server_config).try_into().unwrap());
+  /// let path_contexts = Arc::new(vec![]);
+  ///
+  /// App::new().service(
+  ///     Files::new("/", serve_dir, root_path_context, path_contexts)
+  ///         .guard(Header("Host", "example.com"))
+  /// );
+  /// ```
+  pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
+    self.guards.push(Rc::new(guard));
+    self
+  }
+
   /// Specifies custom guards to use for directory listings and files.
   ///
   /// Default behaviour allows GET and HEAD.
   #[inline]
-  pub fn use_guards<G: Guard + 'static>(mut self, guards: G) -> Self {
-    self.guards = Some(Rc::new(guards));
+  pub fn method_guard<G: Guard + 'static>(mut self, guards: G) -> Self {
+    self.use_guards = Some(Rc::new(guards));
     self
   }
 
@@ -157,7 +198,19 @@ impl Files {
 }
 
 impl HttpServiceFactory for Files {
-  fn register(self, config: &mut AppService) {
+  fn register(mut self, config: &mut AppService) {
+    let guards = if self.guards.is_empty() {
+      None
+    } else {
+      let guards = std::mem::take(&mut self.guards);
+      Some(
+        guards
+          .into_iter()
+          .map(|guard| -> Box<dyn Guard> { Box::new(guard) })
+          .collect::<Vec<_>>(),
+      )
+    };
+
     if self.default.borrow().is_none() {
       *self.default.borrow_mut() = Some(config.default_service());
     }
@@ -168,7 +221,7 @@ impl HttpServiceFactory for Files {
       ResourceDef::prefix(&self.path)
     };
 
-    config.register_service(rdef, None, self, None)
+    config.register_service(rdef, guards, self, None)
   }
 }
 
@@ -187,7 +240,7 @@ impl ServiceFactory<ServiceRequest> for Files {
       None,
       self.root_path_context.clone(),
       self.path_contexts.clone(),
-      self.guards.clone(),
+      self.use_guards.clone(),
     );
 
     if let Some(ref default) = *self.default.borrow() {
@@ -210,835 +263,462 @@ impl ServiceFactory<ServiceRequest> for Files {
 
 #[cfg(test)]
 mod tests {
-  //   use std::fs;
-  //   use std::ops::Add;
-  //   use std::{
-  //     convert::TryInto,
-  //     time::{Duration, SystemTime},
-  //   };
-
-  //   use super::*;
-  //   use crate::config::{ContentDispositionConfig, IndexStrategyConfig, ServerConfig};
-  //   use actix_files::NamedFile;
-  // use actix_web::guard;
-  //   use actix_web::http::header::{self, ContentDisposition, DispositionParam, DispositionType};
-  //   use actix_web::http::{Method, StatusCode};
-  //   use actix_web::middleware::Compress;
-  //   use actix_web::test::{self, TestRequest};
-  //   use actix_web::{web, App, HttpResponse, Responder};
-  //   use bytes::Bytes;
-  //   use fs::File;
-
-  //   fn serve_dir<T: Into<PathBuf>>(path: T) -> Arc<RwLock<Option<PathBuf>>> {
-  //     Arc::new(RwLock::new(Some(path.into())))
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_if_modified_since_without_if_none_match() {
-  //     let file = NamedFile::open("Cargo.toml").unwrap();
-  //     let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
-
-  //     let req = TestRequest::default()
-  //       .header(header::IF_MODIFIED_SINCE, since)
-  //       .to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_if_modified_since_with_if_none_match() {
-  //     let file = NamedFile::open("Cargo.toml").unwrap();
-  //     let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
-
-  //     let req = TestRequest::default()
-  //       .header(header::IF_NONE_MATCH, "miss_etag")
-  //       .header(header::IF_MODIFIED_SINCE, since)
-  //       .to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_text() {
-  //     assert!(NamedFile::open("test--").is_err());
-  //     let mut file = NamedFile::open("Cargo.toml").unwrap();
-  //     {
-  //       file.file();
-  //       let _f: &File = &file;
-  //     }
-  //     {
-  //       let _f: &mut File = &mut file;
-  //     }
-
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //       "text/x-toml"
-  //     );
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //       "inline; filename=\"Cargo.toml\""
-  //     );
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_content_disposition() {
-  //     assert!(NamedFile::open("test--").is_err());
-  //     let mut file = NamedFile::open("Cargo.toml").unwrap();
-  //     {
-  //       file.file();
-  //       let _f: &File = &file;
-  //     }
-  //     {
-  //       let _f: &mut File = &mut file;
-  //     }
-
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //       "inline; filename=\"Cargo.toml\""
-  //     );
-
-  //     let file = NamedFile::open("Cargo.toml")
-  //       .unwrap()
-  //       .disable_content_disposition();
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_non_ascii_file_name() {
-  //     let mut file = NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml").unwrap();
-  //     {
-  //       file.file();
-  //       let _f: &File = &file;
-  //     }
-  //     {
-  //       let _f: &mut File = &mut file;
-  //     }
-
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //       "text/x-toml"
-  //     );
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //       "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
-  //     );
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_set_content_type() {
-  //     let mut file = NamedFile::open("Cargo.toml")
-  //       .unwrap()
-  //       .set_content_type(mime::TEXT_XML);
-  //     {
-  //       file.file();
-  //       let _f: &File = &file;
-  //     }
-  //     {
-  //       let _f: &mut File = &mut file;
-  //     }
-
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //       "text/xml"
-  //     );
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //       "inline; filename=\"Cargo.toml\""
-  //     );
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_image() {
-  //     let mut file = NamedFile::open("tests/test.png").unwrap();
-  //     {
-  //       file.file();
-  //       let _f: &File = &file;
-  //     }
-  //     {
-  //       let _f: &mut File = &mut file;
-  //     }
-
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //       "image/png"
-  //     );
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //       "inline; filename=\"test.png\""
-  //     );
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_image_attachment() {
-  //     let cd = ContentDisposition {
-  //       disposition: DispositionType::Attachment,
-  //       parameters: vec![DispositionParam::Filename(String::from("test.png"))],
-  //     };
-  //     let mut file = NamedFile::open("tests/test.png")
-  //       .unwrap()
-  //       .set_content_disposition(cd);
-  //     {
-  //       file.file();
-  //       let _f: &File = &file;
-  //     }
-  //     {
-  //       let _f: &mut File = &mut file;
-  //     }
-
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //       "image/png"
-  //     );
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //       "attachment; filename=\"test.png\""
-  //     );
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_binary() {
-  //     let mut file = NamedFile::open("tests/test.binary").unwrap();
-  //     {
-  //       file.file();
-  //       let _f: &File = &file;
-  //     }
-  //     {
-  //       let _f: &mut File = &mut file;
-  //     }
-
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //       "application/octet-stream"
-  //     );
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //       "attachment; filename=\"test.binary\""
-  //     );
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_status_code_text() {
-  //     let mut file = NamedFile::open("Cargo.toml")
-  //       .unwrap()
-  //       .set_status_code(StatusCode::NOT_FOUND);
-  //     {
-  //       file.file();
-  //       let _f: &File = &file;
-  //     }
-  //     {
-  //       let _f: &mut File = &mut file;
-  //     }
-
-  //     let req = TestRequest::default().to_http_request();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //       "text/x-toml"
-  //     );
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //       "inline; filename=\"Cargo.toml\""
-  //     );
-  //     assert_eq!(resp.status(), StatusCode::NOT_FOUND);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_mime_override() {
-  //     let mut server_config = ServerConfig::default();
-
-  //     server_config.index_strategy = Some(IndexStrategyConfig::IndexFiles {
-  //       filenames: ["Cargo.toml".to_owned()].iter().cloned().collect(),
-  //     });
-  //     server_config.mime_disposition = Some(
-  //       vec![(
-  //         "text/x-toml".to_owned(),
-  //         ContentDispositionConfig::Attachment,
-  //       )]
-  //       .iter()
-  //       .cloned()
-  //       .collect(),
-  //     );
-
-  //     let root_path_context = Arc::new((&server_config).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().service(Files::new(
-  //       "/",
-  //       serve_dir("."),
-  //       root_path_context,
-  //       path_contexts,
-  //     )))
-  //     .await;
-
-  //     let request = TestRequest::get().uri("/").to_request();
-  //     let response = test::call_service(&mut srv, request).await;
-  //     assert_eq!(response.status(), StatusCode::OK);
-
-  //     let content_disposition = response
-  //       .headers()
-  //       .get(header::CONTENT_DISPOSITION)
-  //       .expect("To have CONTENT_DISPOSITION");
-  //     let content_disposition = content_disposition
-  //       .to_str()
-  //       .expect("Convert CONTENT_DISPOSITION to str");
-  //     assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_ranges_status_code() {
-  //     let mut server_config = ServerConfig::default();
-
-  //     server_config.index_strategy = Some(IndexStrategyConfig::IndexFiles {
-  //       filenames: ["Cargo.toml".to_owned()].iter().cloned().collect(),
-  //     });
-
-  //     let root_path_context = Arc::new((&server_config).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().service(Files::new(
-  //       "/test",
-  //       serve_dir("."),
-  //       root_path_context,
-  //       path_contexts,
-  //     )))
-  //     .await;
-
-  //     // Valid range header
-  //     let request = TestRequest::get()
-  //       .uri("/t%65st/Cargo.toml")
-  //       .header(header::RANGE, "bytes=10-20")
-  //       .to_request();
-  //     let response = test::call_service(&mut srv, request).await;
-  //     assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
-
-  //     // Invalid range header
-  //     let request = TestRequest::get()
-  //       .uri("/t%65st/Cargo.toml")
-  //       .header(header::RANGE, "bytes=1-0")
-  //       .to_request();
-  //     let response = test::call_service(&mut srv, request).await;
-
-  //     assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_content_range_headers() {
-  //     let srv = test::init_service(|| {
-  //       let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //       let path_contexts = Arc::new(vec![]);
-
-  //       App::new().service(Files::new(
-  //         "/",
-  //         serve_dir("."),
-  //         root_path_context,
-  //         path_contexts,
-  //       ))
-  //     });
-
-  //     // Valid range header
-  //     let response = srv
-  //       .get("/tests/test.binary")
-  //       .header(header::RANGE, "bytes=10-20")
-  //       .send()
-  //       .await
-  //       .unwrap();
-  //     let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
-  //     assert_eq!(content_range.to_str().unwrap(), "bytes 10-20/100");
-
-  //     // Invalid range header
-  //     let response = srv
-  //       .get("/tests/test.binary")
-  //       .header(header::RANGE, "bytes=10-5")
-  //       .send()
-  //       .await
-  //       .unwrap();
-  //     let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
-  //     assert_eq!(content_range.to_str().unwrap(), "bytes */100");
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_content_length_headers() {
-  //     let srv = test::init_service(|| {
-  //       let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //       let path_contexts = Arc::new(vec![]);
-
-  //       App::new().service(Files::new(
-  //         "/",
-  //         serve_dir("."),
-  //         root_path_context,
-  //         path_contexts,
-  //       ))
-  //     });
-
-  //     // Valid range header
-  //     let response = srv
-  //       .get("/tests/test.binary")
-  //       .header(header::RANGE, "bytes=10-20")
-  //       .send()
-  //       .await
-  //       .unwrap();
-  //     let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
-  //     assert_eq!(content_length.to_str().unwrap(), "11");
-
-  //     // Valid range header, starting from 0
-  //     let response = srv
-  //       .get("/tests/test.binary")
-  //       .header(header::RANGE, "bytes=0-20")
-  //       .send()
-  //       .await
-  //       .unwrap();
-  //     let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
-  //     assert_eq!(content_length.to_str().unwrap(), "21");
-
-  //     // Without range header
-  //     let mut response = srv.get("/tests/test.binary").send().await.unwrap();
-  //     let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
-  //     assert_eq!(content_length.to_str().unwrap(), "100");
-
-  //     // Should be no transfer-encoding
-  //     let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
-  //     assert!(transfer_encoding.is_none());
-
-  //     // Check file contents
-  //     let bytes = response.body().await.unwrap();
-  //     let data = Bytes::from(fs::read("tests/test.binary").unwrap());
-
-  //     assert_eq!(bytes, data);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_head_content_length_headers() {
-  //     let srv = test::init_service(|| {
-  //       let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //       let path_contexts = Arc::new(vec![]);
-
-  //       App::new().service(Files::new(
-  //         "/",
-  //         serve_dir("."),
-  //         root_path_context,
-  //         path_contexts,
-  //       ))
-  //     });
-
-  //     let response = srv.head("/tests/test.binary").send().await.unwrap();
-
-  //     let content_length = response
-  //       .headers()
-  //       .get(header::CONTENT_LENGTH)
-  //       .unwrap()
-  //       .to_str()
-  //       .unwrap();
-
-  //     assert_eq!(content_length, "100");
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_static_files_with_spaces() {
-  //     let mut server_config = ServerConfig::default();
-
-  //     server_config.index_strategy = Some(IndexStrategyConfig::IndexFiles {
-  //       filenames: ["Cargo.toml".to_owned()].iter().cloned().collect(),
-  //     });
-
-  //     let root_path_context = Arc::new((&server_config).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().service(Files::new(
-  //       "/",
-  //       serve_dir("."),
-  //       root_path_context,
-  //       path_contexts,
-  //     )))
-  //     .await;
-  //     let request = TestRequest::get()
-  //       .uri("/tests/test%20space.binary")
-  //       .to_request();
-  //     let response = test::call_service(&mut srv, request).await;
-  //     assert_eq!(response.status(), StatusCode::OK);
-
-  //     let bytes = test::read_body(response).await;
-  //     let data = Bytes::from(fs::read("tests/test space.binary").unwrap());
-  //     assert_eq!(bytes, data);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_files_not_allowed() {
-  //     let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().default_service(Files::new(
-  //       "/",
-  //       serve_dir("."),
-  //       root_path_context,
-  //       path_contexts,
-  //     )))
-  //     .await;
-
-  //     let req = TestRequest::default()
-  //       .uri("/Cargo.toml")
-  //       .method(Method::POST)
-  //       .to_request();
-
-  //     let resp = test::call_service(&mut srv, req).await;
-  //     assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
-
-  //     let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().default_service(Files::new(
-  //       "/",
-  //       serve_dir("."),
-  //       root_path_context,
-  //       path_contexts,
-  //     )))
-  //     .await;
-  //     let req = TestRequest::default()
-  //       .method(Method::PUT)
-  //       .uri("/Cargo.toml")
-  //       .to_request();
-  //     let resp = test::call_service(&mut srv, req).await;
-  //     assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_files_guards() {
-  //     let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().service(
-  //       Files::new("/", serve_dir("."), root_path_context, path_contexts).use_guards(guard::Post()),
-  //     ))
-  //     .await;
-
-  //     let req = TestRequest::default()
-  //       .uri("/Cargo.toml")
-  //       .method(Method::POST)
-  //       .to_request();
-
-  //     let resp = test::call_service(&mut srv, req).await;
-  //     assert_eq!(resp.status(), StatusCode::OK);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_content_encoding() {
-  //     let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
-  //       web::resource("/").to(|| async {
-  //         NamedFile::open("Cargo.toml")
-  //           .unwrap()
-  //           .set_content_encoding(header::ContentEncoding::Identity)
-  //       }),
-  //     ))
-  //     .await;
-
-  //     let request = TestRequest::get()
-  //       .uri("/")
-  //       .header(header::ACCEPT_ENCODING, "gzip")
-  //       .to_request();
-  //     let res = test::call_service(&mut srv, request).await;
-  //     assert_eq!(res.status(), StatusCode::OK);
-  //     assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_content_encoding_gzip() {
-  //     let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
-  //       web::resource("/").to(|| async {
-  //         NamedFile::open("Cargo.toml")
-  //           .unwrap()
-  //           .set_content_encoding(header::ContentEncoding::Gzip)
-  //       }),
-  //     ))
-  //     .await;
-
-  //     let request = TestRequest::get()
-  //       .uri("/")
-  //       .header(header::ACCEPT_ENCODING, "gzip")
-  //       .to_request();
-  //     let res = test::call_service(&mut srv, request).await;
-  //     assert_eq!(res.status(), StatusCode::OK);
-  //     assert_eq!(
-  //       res
-  //         .headers()
-  //         .get(header::CONTENT_ENCODING)
-  //         .unwrap()
-  //         .to_str()
-  //         .unwrap(),
-  //       "gzip"
-  //     );
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_named_file_allowed_method() {
-  //     let req = TestRequest::default().method(Method::GET).to_http_request();
-  //     let file = NamedFile::open("Cargo.toml").unwrap();
-  //     let resp = file.respond_to(&req).await.unwrap();
-  //     assert_eq!(resp.status(), StatusCode::OK);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_static_files() {
-  //     let mut server_config = ServerConfig::default();
-
-  //     server_config.index_strategy = Some(IndexStrategyConfig::AlwaysShowListing);
-
-  //     let root_path_context = Arc::new((&server_config).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().service(Files::new(
-  //       "/",
-  //       serve_dir("."),
-  //       root_path_context,
-  //       path_contexts,
-  //     )))
-  //     .await;
-  //     let req = TestRequest::with_uri("/missing").to_request();
-
-  //     let resp = test::call_service(&mut srv, req).await;
-  //     assert_eq!(resp.status(), StatusCode::NOT_FOUND);
-
-  //     // Without index strategy.
-  //     let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().service(Files::new(
-  //       "/",
-  //       serve_dir("."),
-  //       root_path_context,
-  //       path_contexts,
-  //     )))
-  //     .await;
-
-  //     let req = TestRequest::default().to_request();
-  //     let resp = test::call_service(&mut srv, req).await;
-  //     assert_eq!(resp.status(), StatusCode::NOT_FOUND);
-
-  //     // With default renderer.
-  //     server_config.index_strategy = Some(IndexStrategyConfig::AlwaysShowListing);
-
-  //     let root_path_context = Arc::new((&server_config).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut srv = test::init_service(App::new().service(Files::new(
-  //       "/",
-  //       serve_dir("."),
-  //       root_path_context,
-  //       path_contexts,
-  //     )))
-  //     .await;
-  //     let req = TestRequest::with_uri("/tests").to_request();
-  //     let resp = test::call_service(&mut srv, req).await;
-  //     assert_eq!(
-  //       resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //       "text/html; charset=utf-8"
-  //     );
-
-  //     let bytes = test::read_body(resp).await;
-  //     assert!(format!("{:?}", bytes).contains("/tests/test.png"));
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_redirect_to_slash_directory() {
-  //     let mut server_config = ServerConfig::default();
-
-  //     server_config.index_strategy = Some(IndexStrategyConfig::IndexFiles {
-  //       filenames: ["test.png".to_owned()].iter().cloned().collect(),
-  //     });
-
-  //     let root_path_context = Arc::new((&server_config).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     // should not redirect if no index
-  //     // let mut srv = test::init_service(
-  //     //   App::new().service(Files::new("/", serve_dir(".")).redirect_to_slash_directory()),
-  //     // )
-  //     // .await;
-  //     // let req = TestRequest::with_uri("/tests").to_request();
-  //     // let resp = test::call_service(&mut srv, req).await;
-  //     // assert_eq!(resp.status(), StatusCode::NOT_FOUND);
-
-  //     // should redirect if index present
-  //     let mut srv = test::init_service(
-  //       App::new().service(
-  //         Files::new("/", serve_dir("."), root_path_context, path_contexts)
-  //           .redirect_to_slash_directory(),
-  //       ),
-  //     )
-  //     .await;
-  //     let req = TestRequest::with_uri("/tests").to_request();
-  //     let resp = test::call_service(&mut srv, req).await;
-  //     assert_eq!(resp.status(), StatusCode::FOUND);
-
-  //     // should not redirect if the path is wrong
-  //     let req = TestRequest::with_uri("/not_existing").to_request();
-  //     let resp = test::call_service(&mut srv, req).await;
-  //     assert_eq!(resp.status(), StatusCode::NOT_FOUND);
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_static_files_bad_directory() {
-  //     let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let _st: Files = Files::new("/", serve_dir("missing"), root_path_context, path_contexts);
-
-  //     let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let _st: Files = Files::new(
-  //       "/",
-  //       serve_dir("Cargo.toml"),
-  //       root_path_context,
-  //       path_contexts,
-  //     );
-  //   }
-
-  //   #[actix_rt::test]
-  //   async fn test_default_handler_file_missing() {
-  //     let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
-  //     let path_contexts = Arc::new(vec![]);
-
-  //     let mut st = Files::new("/", serve_dir("."), root_path_context, path_contexts)
-  //       .default_handler(|req: ServiceRequest| {
-  //         ok(req.into_response(HttpResponse::Ok().body("default content")))
-  //       })
-  //       .new_service(())
-  //       .await
-  //       .unwrap();
-  //     let req = TestRequest::with_uri("/missing").to_srv_request();
-
-  //     let resp = test::call_service(&mut st, req).await;
-  //     assert_eq!(resp.status(), StatusCode::OK);
-  //     let bytes = test::read_body(resp).await;
-  //     assert_eq!(bytes, Bytes::from_static(b"default content"));
-  //   }
-
-  //     #[actix_rt::test]
-  //     async fn test_serve_index() {
-  //         let st = Files::new(".").index_file("test.binary");
-  //         let req = TestRequest::default().uri("/tests").finish();
-
-  //         let resp = st.handle(&req).respond_to(&req).unwrap();
-  //         let resp = resp.as_msg();
-  //         assert_eq!(resp.status(), StatusCode::OK);
-  //         assert_eq!(
-  //             resp.headers()
-  //                 .get(header::CONTENT_TYPE)
-  //                 .expect("content type"),
-  //             "application/octet-stream"
-  //         );
-  //         assert_eq!(
-  //             resp.headers()
-  //                 .get(header::CONTENT_DISPOSITION)
-  //                 .expect("content disposition"),
-  //             "attachment; filename=\"test.binary\""
-  //         );
-
-  //         let req = TestRequest::default().uri("/tests/").finish();
-  //         let resp = st.handle(&req).respond_to(&req).unwrap();
-  //         let resp = resp.as_msg();
-  //         assert_eq!(resp.status(), StatusCode::OK);
-  //         assert_eq!(
-  //             resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //             "application/octet-stream"
-  //         );
-  //         assert_eq!(
-  //             resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //             "attachment; filename=\"test.binary\""
-  //         );
-
-  //         // nonexistent index file
-  //         let req = TestRequest::default().uri("/tests/unknown").finish();
-  //         let resp = st.handle(&req).respond_to(&req).unwrap();
-  //         let resp = resp.as_msg();
-  //         assert_eq!(resp.status(), StatusCode::NOT_FOUND);
-
-  //         let req = TestRequest::default().uri("/tests/unknown/").finish();
-  //         let resp = st.handle(&req).respond_to(&req).unwrap();
-  //         let resp = resp.as_msg();
-  //         assert_eq!(resp.status(), StatusCode::NOT_FOUND);
-  //     }
-
-  //     #[actix_rt::test]
-  //     async fn test_serve_index_nested() {
-  //         let st = Files::new(".").index_file("mod.rs");
-  //         let req = TestRequest::default().uri("/src/client").finish();
-  //         let resp = st.handle(&req).respond_to(&req).unwrap();
-  //         let resp = resp.as_msg();
-  //         assert_eq!(resp.status(), StatusCode::OK);
-  //         assert_eq!(
-  //             resp.headers().get(header::CONTENT_TYPE).unwrap(),
-  //             "text/x-rust"
-  //         );
-  //         assert_eq!(
-  //             resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
-  //             "inline; filename=\"mod.rs\""
-  //         );
-  //     }
-
-  //     #[actix_rt::test]
-  //     fn integration_serve_index() {
-  //         let mut srv = test::TestServer::with_factory(|| {
-  //             App::new().handler(
-  //                 "test",
-  //                 Files::new(".").index_file("Cargo.toml"),
-  //             )
-  //         });
-
-  //         let request = srv.get().uri(srv.url("/test")).finish().unwrap();
-  //         let response = srv.execute(request.send()).unwrap();
-  //         assert_eq!(response.status(), StatusCode::OK);
-  //         let bytes = srv.execute(response.body()).unwrap();
-  //         let data = Bytes::from(fs::read("Cargo.toml").unwrap());
-  //         assert_eq!(bytes, data);
-
-  //         let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
-  //         let response = srv.execute(request.send()).unwrap();
-  //         assert_eq!(response.status(), StatusCode::OK);
-  //         let bytes = srv.execute(response.body()).unwrap();
-  //         let data = Bytes::from(fs::read("Cargo.toml").unwrap());
-  //         assert_eq!(bytes, data);
-
-  //         // nonexistent index file
-  //         let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap();
-  //         let response = srv.execute(request.send()).unwrap();
-  //         assert_eq!(response.status(), StatusCode::NOT_FOUND);
-
-  //         let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap();
-  //         let response = srv.execute(request.send()).unwrap();
-  //         assert_eq!(response.status(), StatusCode::NOT_FOUND);
-  //     }
-
-  //     #[actix_rt::test]
-  //     fn integration_percent_encoded() {
-  //         let mut srv = test::TestServer::with_factory(|| {
-  //             App::new().handler(
-  //                 "test",
-  //                 Files::new(".").index_file("Cargo.toml"),
-  //             )
-  //         });
-
-  //         let request = srv
-  //             .get()
-  //             .uri(srv.url("/test/%43argo.toml"))
-  //             .finish()
-  //             .unwrap();
-  //         let response = srv.execute(request.send()).unwrap();
-  //         assert_eq!(response.status(), StatusCode::OK);
-  //     }
+  use std::collections::{HashMap, HashSet};
+  use std::convert::TryInto;
+  use std::fs;
+
+  use super::*;
+  use crate::config::{ContentDispositionConfig, IndexStrategyConfig, ServerConfig};
+  use actix_web::guard;
+
+  use actix_web::http::{header, Method, StatusCode};
+  use actix_web::test::{self, TestRequest};
+  use actix_web::App;
+  use bytes::Bytes;
+
+  fn serve_dir<T: Into<PathBuf>>(path: T) -> Arc<RwLock<Option<PathBuf>>> {
+    Arc::new(RwLock::new(Some(path.into())))
+  }
+
+  #[actix_rt::test]
+  async fn test_mime_override() {
+    let server_config = ServerConfig {
+      index_strategy: Some(IndexStrategyConfig::IndexFiles {
+        filenames: HashSet::from(["Cargo.toml".to_owned()]),
+      }),
+      mime_disposition: Some(HashMap::from([(
+        "text/x-toml".to_owned(),
+        ContentDispositionConfig::Attachment,
+      )])),
+      ..ServerConfig::default()
+    };
+
+    let root_path_context = Arc::new((&server_config).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+
+    let request = TestRequest::get().uri("/").to_request();
+    let response = test::call_service(&mut srv, request).await;
+    assert_eq!(response.status(), StatusCode::OK);
+
+    let content_disposition = response
+      .headers()
+      .get(header::CONTENT_DISPOSITION)
+      .expect("To have CONTENT_DISPOSITION");
+    let content_disposition = content_disposition
+      .to_str()
+      .expect("Convert CONTENT_DISPOSITION to str");
+    assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
+  }
+
+  #[actix_rt::test]
+  async fn test_named_file_ranges_status_code() {
+    let mut server_config = ServerConfig::default();
+
+    server_config.index_strategy = Some(IndexStrategyConfig::IndexFiles {
+      filenames: ["Cargo.toml".to_owned()].iter().cloned().collect(),
+    });
+
+    let root_path_context = Arc::new((&server_config).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().service(Files::new(
+      "/test",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+
+    // Valid range header
+    let request = TestRequest::get()
+      .uri("/t%65st/Cargo.toml")
+      .append_header((header::RANGE, "bytes=10-20"))
+      .to_request();
+    let response = test::call_service(&mut srv, request).await;
+    assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
+
+    // Invalid range header
+    let request = TestRequest::get()
+      .uri("/t%65st/Cargo.toml")
+      .append_header((header::RANGE, "bytes=1-0"))
+      .to_request();
+    let response = test::call_service(&mut srv, request).await;
+
+    assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
+  }
+
+  #[actix_rt::test]
+  async fn test_named_file_content_range_headers() {
+    let srv = actix_test::start(|| {
+      let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+      let path_contexts = Arc::new(vec![]);
+
+      App::new().service(Files::new(
+        "/",
+        serve_dir("."),
+        root_path_context,
+        path_contexts,
+      ))
+    });
+
+    // Valid range header
+    let response = srv
+      .get("/tests/test.binary")
+      .append_header((header::RANGE, "bytes=10-20"))
+      .send()
+      .await
+      .unwrap();
+    let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
+    assert_eq!(content_range.to_str().unwrap(), "bytes 10-20/100");
+
+    // Invalid range header
+    let response = srv
+      .get("/tests/test.binary")
+      .append_header((header::RANGE, "bytes=10-5"))
+      .send()
+      .await
+      .unwrap();
+    let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
+    assert_eq!(content_range.to_str().unwrap(), "bytes */100");
+  }
+
+  #[actix_rt::test]
+  async fn test_named_file_content_length_headers() {
+    let srv = actix_test::start(|| {
+      let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+      let path_contexts = Arc::new(vec![]);
+
+      App::new().service(Files::new(
+        "/",
+        serve_dir("."),
+        root_path_context,
+        path_contexts,
+      ))
+    });
+
+    // Valid range header
+    let response = srv
+      .get("/tests/test.binary")
+      .append_header((header::RANGE, "bytes=10-20"))
+      .send()
+      .await
+      .unwrap();
+    let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
+    assert_eq!(content_length.to_str().unwrap(), "11");
+
+    // Valid range header, starting from 0
+    let response = srv
+      .get("/tests/test.binary")
+      .append_header((header::RANGE, "bytes=0-20"))
+      .send()
+      .await
+      .unwrap();
+    let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
+    assert_eq!(content_length.to_str().unwrap(), "21");
+
+    // Without range header
+    let mut response = srv.get("/tests/test.binary").send().await.unwrap();
+    let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
+    assert_eq!(content_length.to_str().unwrap(), "100");
+
+    // Should be no transfer-encoding
+    let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
+    assert!(transfer_encoding.is_none());
+
+    // Check file contents
+    let bytes = response.body().await.unwrap();
+    let data = Bytes::from(fs::read("tests/test.binary").unwrap());
+
+    assert_eq!(bytes, data);
+  }
+
+  #[actix_rt::test]
+  async fn test_head_content_length_headers() {
+    let srv = actix_test::start(|| {
+      let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+      let path_contexts = Arc::new(vec![]);
+
+      App::new().service(Files::new(
+        "/",
+        serve_dir("."),
+        root_path_context,
+        path_contexts,
+      ))
+    });
+
+    let response = srv.head("/tests/test.binary").send().await.unwrap();
+
+    let content_length = response
+      .headers()
+      .get(header::CONTENT_LENGTH)
+      .unwrap()
+      .to_str()
+      .unwrap();
+
+    assert_eq!(content_length, "100");
+  }
+
+  #[actix_rt::test]
+  async fn test_static_files_with_spaces() {
+    let mut server_config = ServerConfig::default();
+
+    server_config.index_strategy = Some(IndexStrategyConfig::IndexFiles {
+      filenames: ["Cargo.toml".to_owned()].iter().cloned().collect(),
+    });
+
+    let root_path_context = Arc::new((&server_config).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+    let request = TestRequest::get()
+      .uri("/tests/test%20space.binary")
+      .to_request();
+    let response = test::call_service(&mut srv, request).await;
+    assert_eq!(response.status(), StatusCode::OK);
+
+    let bytes = test::read_body(response).await;
+    let data = Bytes::from(fs::read("tests/test space.binary").unwrap());
+    assert_eq!(bytes, data);
+  }
+
+  #[actix_rt::test]
+  async fn test_files_not_allowed() {
+    let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().default_service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+
+    let req = TestRequest::default()
+      .uri("/Cargo.toml")
+      .method(Method::POST)
+      .to_request();
+
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
+
+    let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().default_service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+    let req = TestRequest::default()
+      .method(Method::PUT)
+      .uri("/Cargo.toml")
+      .to_request();
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
+  }
+
+  #[actix_rt::test]
+  async fn test_files_guards() {
+    let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let srv = test::init_service(App::new().service(
+      Files::new("/", serve_dir("."), root_path_context, path_contexts).method_guard(guard::Post()),
+    ))
+    .await;
+
+    let req = TestRequest::default()
+      .uri("/Cargo.toml")
+      .method(Method::POST)
+      .to_request();
+
+    let resp = test::call_service(&srv, req).await;
+    assert_eq!(resp.status(), StatusCode::OK);
+  }
+
+  #[actix_rt::test]
+  async fn test_static_files() {
+    let server_config = ServerConfig {
+      index_strategy: Some(IndexStrategyConfig::AlwaysShowListing),
+      ..ServerConfig::default()
+    };
+
+    let root_path_context = Arc::new((&server_config).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+    let req = TestRequest::with_uri("/tests/test.png").to_request();
+
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::OK);
+
+    let bytes = test::read_body(resp).await;
+
+    let data = Bytes::from(fs::read("tests/test.png").unwrap());
+    assert_eq!(bytes, data);
+  }
+
+  #[actix_rt::test]
+  async fn test_static_files_percent_encoded() {
+    let server_config = ServerConfig {
+      index_strategy: Some(IndexStrategyConfig::AlwaysShowListing),
+      ..ServerConfig::default()
+    };
+
+    let root_path_context = Arc::new((&server_config).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+    let req = TestRequest::with_uri("/%43argo.toml").to_request();
+
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::OK);
+  }
+
+  #[actix_rt::test]
+  async fn test_static_files_with_missing_path() {
+    let server_config = ServerConfig {
+      index_strategy: Some(IndexStrategyConfig::AlwaysShowListing),
+      ..ServerConfig::default()
+    };
+
+    let root_path_context = Arc::new((&server_config).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+    let req = TestRequest::with_uri("/missing").to_request();
+
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+  }
+
+  #[actix_rt::test]
+  async fn test_static_files_without_index_strategy() {
+    let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+
+    let req = TestRequest::default().to_request();
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+  }
+
+  #[actix_rt::test]
+  async fn test_static_files_with_listing_index_strategy() {
+    let server_config = ServerConfig {
+      index_strategy: Some(IndexStrategyConfig::AlwaysShowListing),
+      ..ServerConfig::default()
+    };
+
+    let root_path_context = Arc::new((&server_config).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let mut srv = test::init_service(App::new().default_service(Files::new(
+      "/",
+      serve_dir("."),
+      root_path_context,
+      path_contexts,
+    )))
+    .await;
+
+    let req = TestRequest::with_uri("/tests").to_request();
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::OK);
+    assert_eq!(
+      resp.headers().get(header::CONTENT_TYPE).unwrap(),
+      "text/html; charset=utf-8"
+    );
+
+    let bytes = test::read_body(resp).await;
+    assert!(format!("{:?}", bytes).contains("/tests/test.png"));
+  }
+
+  #[actix_rt::test]
+  async fn test_redirect_to_slash_directory() {
+    let mut server_config = ServerConfig::default();
+
+    server_config.index_strategy = Some(IndexStrategyConfig::IndexFiles {
+      filenames: ["test.png".to_owned()].iter().cloned().collect(),
+    });
+
+    let root_path_context: Arc<PathContext> = Arc::new((&server_config).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    // should redirect if index present
+    let mut srv = test::init_service(
+      App::new().service(
+        Files::new("/", serve_dir("."), root_path_context, path_contexts)
+          .redirect_to_slash_directory(),
+      ),
+    )
+    .await;
+    let req = TestRequest::with_uri("/tests").to_request();
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::FOUND);
+
+    // should not redirect if the path is wrong
+    let req = TestRequest::with_uri("/not_existing").to_request();
+    let resp = test::call_service(&mut srv, req).await;
+    assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+  }
+
+  #[actix_rt::test]
+  async fn test_static_files_bad_directory() {
+    let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let _st: Files = Files::new("/", serve_dir("missing"), root_path_context, path_contexts);
+
+    let root_path_context = Arc::new((&ServerConfig::default()).try_into().unwrap());
+    let path_contexts = Arc::new(vec![]);
+
+    let _st: Files = Files::new(
+      "/",
+      serve_dir("Cargo.toml"),
+      root_path_context,
+      path_contexts,
+    );
+  }
 }
-- 
GitLab