ptth/crates/ptth_server/src/file_server/tests.rs

403 lines
8.8 KiB
Rust

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);
}
}