use ehttpd::bytes::Data;
use ehttpd::http::Response;
use std::fs::{self, File};
use std::io::{Seek, SeekFrom, Write};
use std::path::Path;
use std::process;
#[test]
fn response_is_written() {
let mut response = Response::new_status_reason(201, "Created");
response.set_body_data("hello");
response.set_content_type("text/plain");
let mut output = Vec::new();
response.to_stream(&mut output).expect("failed to write response");
let expected = concat! {
"HTTP/1.1 201 Created\r\n",
"Content-Length: 5\r\n",
"Content-Type: text/plain\r\n",
"\r\n",
"hello",
};
assert_eq!(output, expected.as_bytes());
}
#[test]
fn field_is_replaced_case_insensitively() {
let mut response = Response::new_200_ok();
response.set_field("X-Test", "first");
response.set_field("x-test", "second");
let mut fields = response.fields.iter().filter(|(key, _)| key.eq_ignore_ascii_case(b"X-Test"));
let field = fields.next().expect("response field is missing");
assert!(fields.next().is_none());
assert_eq!(field.0, b"x-test");
assert_eq!(field.1, b"second");
}
#[test]
fn connection_close_is_recognized_inside_a_token_list() {
let mut response = Response::new_200_ok();
response.set_field("Connection", "keep-alive, Close, upgrade");
assert!(response.has_connection_close());
}
#[test]
fn head_response_preserves_content_length() {
let mut response = Response::new_200_ok();
response.set_body_data("hello");
response.make_head();
let mut output = Vec::new();
response.to_stream(&mut output).expect("failed to write response");
let expected = concat! {
"HTTP/1.1 200 OK\r\n",
"Content-Length: 5\r\n",
"\r\n",
};
assert_eq!(output, expected.as_bytes());
}
#[test]
fn status_helpers_set_status_and_reason() {
let responses = [
(Response::new_200_ok(), "200", "OK"),
(Response::new_400_badrequest(), "400", "Bad Request"),
(Response::new_403_forbidden(), "403", "Forbidden"),
(Response::new_404_notfound(), "404", "Not Found"),
(Response::new_405_methodnotallowed(), "405", "Method Not Allowed"),
(Response::new_413_payloadtoolarge(), "413", "Payload Too Large"),
(Response::new_416_rangenotsatisfiable(), "416", "Range Not Satisfiable"),
(Response::new_500_internalservererror(), "500", "Internal Server Error"),
];
for (response, status, reason) in responses {
assert_eq!(response.version, b"HTTP/1.1");
assert_eq!(response.status, status);
assert_eq!(response.reason, reason);
assert_eq!(response.content_length().expect("invalid content length"), Some(0));
}
}
#[test]
fn redirect_and_authentication_helpers_set_fields() {
let responses = [
(Response::new_303_seeother("/next"), "Location", "/next"),
(Response::new_307_temporaryredirect("/later"), "Location", "/later"),
(Response::new_401_unauthorized("Basic"), "WWW-Authenticate", "Basic"),
];
for (response, expected_key, expected_value) in responses {
let (_, value) = (response.fields)
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(expected_key.as_bytes()))
.expect("response field is missing");
assert_eq!(value, expected_value);
}
}
#[test]
fn file_body_uses_current_offset() {
let path = Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("ehttpd-response-{}.tmp", process::id()));
let mut file = File::options()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&path)
.expect("failed to create response file");
file.write_all(b"<BODY>").expect("failed to write response file");
file.seek(SeekFrom::Start(2)).expect("failed to seek response file");
let mut response = Response::new_200_ok();
response.set_body_file(file).expect("failed to set file body");
let mut output = Vec::new();
response.to_stream(&mut output).expect("failed to write response");
drop(response);
fs::remove_file(path).expect("failed to remove response file");
let expected = concat! {
"HTTP/1.1 200 OK\r\n",
"Content-Length: 4\r\n",
"\r\n",
"ODY>",
};
assert_eq!(output, expected.as_bytes());
}
#[test]
fn invalid_content_length_is_rejected() {
let mut response = Response::new(Data::from(b"HTTP/1.1"), Data::from(b"200"), Data::from(b"OK"));
response.set_field("Content-Length", "invalid");
assert!(response.content_length().is_err());
}