use std::{ ffi::OsStr, path::{ Component, Path, }, }; use maplit::*; use tokio::runtime::Runtime; #[test] fn pretty_print_bytes () { for (input_after, expected_before, expected_after) in vec! [ (1, "0 B", "1 B"), (1024, "1023 B", "1 KiB"), (1024 + 512, "1 KiB", "2 KiB"), (1023 * 1024 + 512, "1023 KiB", "1 MiB"), ((1024 + 512) * 1024, "1 MiB", "2 MiB"), (1023 * 1024 * 1024 + 512 * 1024, "1023 MiB", "1 GiB"), ((1024 + 512) * 1024 * 1024, "1 GiB", "2 GiB"), ].into_iter () { let actual = super::pretty_print_bytes (input_after - 1); assert_eq! (&actual, expected_before); let actual = super::pretty_print_bytes (input_after); assert_eq! (&actual, expected_after); } } #[test] fn i_hate_paths () { let mut components = Path::new ("/home/user").components (); assert_eq! (components.next (), Some (Component::RootDir)); assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home")))); assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user")))); assert_eq! (components.next (), None); let mut components = Path::new ("./home/user").components (); assert_eq! (components.next (), Some (Component::CurDir)); assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home")))); assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user")))); assert_eq! (components.next (), None); let mut components = Path::new (".").components (); assert_eq! (components.next (), Some (Component::CurDir)); assert_eq! (components.next (), None); } #[test] fn file_server () { use std::path::PathBuf; #[cfg (test)] use always_equal::test::AlwaysEqual; use ptth_core::{ http_serde::Method, }; use super::*; tracing_subscriber::fmt ().try_init ().ok (); let rt = Runtime::new ().expect ("Can't create runtime"); rt.block_on (async { let files_root = PathBuf::from ("./"); let dirs_roots = Default::default (); let roots = internal::FileRoots { files: &files_root, dirs: &dirs_roots, }; let headers = Default::default (); { use internal::Response::*; use crate::file_server::FileServerError; for (uri_path, expected) in vec! [ ("/", Root), ("/files", NotFound), ("/files/?", InvalidQuery), ("/files/src", Redirect ("src/".to_string ())), ("/files/src/?", InvalidQuery), ("/files/test/test.md", ServeFile (internal::ServeFileParams { send_body: true, range: range::ValidParsed { range: 0..144, range_requested: false, }, file: AlwaysEqual::testing_blank (), })), ] { let resp = internal::serve_all ( roots, Method::Get, uri_path, &headers, None ).await; assert_eq! (resp.expect ("This block only tests Ok (_) responses"), expected); } for (uri_path, checker) in vec! [ ("/ ", |e| match e { FileServerError::InvalidUri (_) => (), e => panic! ("Expected InvalidUri, got {:?}", e), }), ] { let resp = internal::serve_all ( roots, Method::Get, uri_path, &headers, None ).await; checker (resp.unwrap_err ()); } } }); } #[test] fn parse_uri () { assert! (http::Uri::from_maybe_shared ("/").is_ok ()); } #[test] fn serve_file_decision () { use ptth_core::http_serde::StatusCode; use super::{ ServeFileInput, ServeFileOutput, }; for (input, expected) in vec! [ // Regular HEAD requests ( ServeFileInput { if_none_match: None, actual_etag: None, client_wants_body: false, range_requested: false, }, ServeFileOutput { status_code: StatusCode::NoContent, should_send_body: false, } ), ( ServeFileInput { if_none_match: None, actual_etag: None, client_wants_body: false, range_requested: true, }, ServeFileOutput { status_code: StatusCode::NoContent, should_send_body: false, } ), // Regular GET requests ( ServeFileInput { if_none_match: None, actual_etag: None, client_wants_body: true, range_requested: false, }, ServeFileOutput { status_code: StatusCode::Ok, should_send_body: true, } ), ( ServeFileInput { if_none_match: None, actual_etag: None, client_wants_body: true, range_requested: true, }, ServeFileOutput { status_code: StatusCode::PartialContent, should_send_body: true, } ), // HEAD requests where we pull a valid etag from the FS ( ServeFileInput { if_none_match: None, actual_etag: Some (b"bogus_2".to_vec ()), client_wants_body: false, range_requested: false, }, ServeFileOutput { status_code: StatusCode::NoContent, should_send_body: false, } ), ( ServeFileInput { if_none_match: None, actual_etag: Some (b"bogus_2".to_vec ()), client_wants_body: false, range_requested: true, }, ServeFileOutput { status_code: StatusCode::NoContent, should_send_body: false, } ), ( ServeFileInput { if_none_match: None, actual_etag: Some (b"bogus_2".to_vec ()), client_wants_body: true, range_requested: false, }, ServeFileOutput { status_code: StatusCode::Ok, should_send_body: true, } ), ( ServeFileInput { if_none_match: None, actual_etag: Some (b"bogus_2".to_vec ()), client_wants_body: true, range_requested: true, }, ServeFileOutput { status_code: StatusCode::PartialContent, should_send_body: true, } ), // Client has an expected ETag but we can't pull the real one for // some reason ( ServeFileInput { if_none_match: Some (b"bogus_1"), actual_etag: None, client_wants_body: false, range_requested: false, }, ServeFileOutput { status_code: StatusCode::NoContent, should_send_body: false, } ), ( ServeFileInput { if_none_match: Some (b"bogus_1"), actual_etag: None, client_wants_body: false, range_requested: true, }, ServeFileOutput { status_code: StatusCode::NoContent, should_send_body: false, } ), ( ServeFileInput { if_none_match: Some (b"bogus_1"), actual_etag: None, client_wants_body: true, range_requested: false, }, ServeFileOutput { status_code: StatusCode::Ok, should_send_body: true, } ), ( ServeFileInput { if_none_match: Some (b"bogus_1"), actual_etag: None, client_wants_body: true, range_requested: true, }, ServeFileOutput { status_code: StatusCode::PartialContent, should_send_body: true, } ), // File changed on disk since the client last saw it ( ServeFileInput { if_none_match: Some (b"bogus_1"), actual_etag: Some (b"bogus_2".to_vec ()), client_wants_body: false, range_requested: false, }, ServeFileOutput { status_code: StatusCode::NoContent, should_send_body: false, } ), ( ServeFileInput { if_none_match: Some (b"bogus_1"), actual_etag: Some (b"bogus_2".to_vec ()), client_wants_body: false, range_requested: true, }, ServeFileOutput { status_code: StatusCode::NoContent, should_send_body: false, } ), ( ServeFileInput { if_none_match: Some (b"bogus_1"), actual_etag: Some (b"bogus_2".to_vec ()), client_wants_body: true, range_requested: false, }, ServeFileOutput { status_code: StatusCode::Ok, should_send_body: true, } ), ( ServeFileInput { if_none_match: Some (b"bogus_1"), actual_etag: Some (b"bogus_2".to_vec ()), client_wants_body: true, range_requested: true, }, ServeFileOutput { status_code: StatusCode::PartialContent, should_send_body: true, } ), // The ETags match, and we can tell the client to use their cache ( ServeFileInput { if_none_match: Some (b"bogus_3"), actual_etag: Some (b"bogus_3".to_vec ()), client_wants_body: false, range_requested: false, }, ServeFileOutput { status_code: StatusCode::NotModified, should_send_body: false, } ), ( ServeFileInput { if_none_match: Some (b"bogus_3"), actual_etag: Some (b"bogus_3".to_vec ()), client_wants_body: false, range_requested: true, }, ServeFileOutput { status_code: StatusCode::NotModified, should_send_body: false, } ), ( ServeFileInput { if_none_match: Some (b"bogus_3"), actual_etag: Some (b"bogus_3".to_vec ()), client_wants_body: true, range_requested: false, }, ServeFileOutput { status_code: StatusCode::NotModified, should_send_body: false, } ), ( ServeFileInput { if_none_match: Some (b"bogus_3"), actual_etag: Some (b"bogus_3".to_vec ()), client_wants_body: true, range_requested: true, }, ServeFileOutput { status_code: StatusCode::NotModified, should_send_body: false, } ), ].into_iter () { let actual = super::serve_file_decision (&input); assert_eq! (actual, expected, "{:?}", input); } }