use http::{Request, Response, StatusCode, Version};
use crate::CloseReason;
use crate::client::test::scenario::write_response;
use super::scenario::Scenario;
#[test]
fn reuse_without_send_body() {
let scenario = Scenario::builder()
.get("https://a.test")
.response(Response::new(()))
.recv_body("hello", false)
.build();
let call = scenario.to_cleanup();
assert!(!call.must_close_connection());
}
#[test]
fn reuse_with_send_body() {
let scenario = Scenario::builder()
.post("https://a.test")
.send_body("hi", false)
.response(Response::new(()))
.recv_body("hello", false)
.build();
let call = scenario.to_cleanup();
assert!(!call.must_close_connection());
}
#[test]
fn reuse_without_recv_body() {
let scenario = Scenario::builder()
.head("https://a.test")
.response(Response::new(()))
.build();
let call = scenario.to_cleanup();
assert!(!call.must_close_connection());
}
#[test]
fn reuse_after_redirect() {
let scenario = Scenario::builder()
.get("https://a.test")
.redirect(StatusCode::FOUND, "https://b.test")
.build();
let call = scenario.to_cleanup();
assert!(!call.must_close_connection());
}
#[test]
fn close_due_to_http10() {
let scenario = Scenario::builder()
.request(
Request::get("https://a.test")
.version(Version::HTTP_10)
.body(())
.unwrap(),
)
.build();
let call = scenario.to_cleanup();
let inner = call.inner();
assert_eq!(
*inner.close_reason.first().unwrap(),
CloseReason::CloseDelimitedBody
);
assert!(call.must_close_connection());
}
#[test]
fn http10_response_persistence() {
for (connection, must_close) in [
(None, true),
(Some("Keep-Alive"), false),
(Some("other, KEEP-ALIVE"), false),
(Some("keep-alive, close"), true),
] {
let mut response = Response::builder()
.version(Version::HTTP_10)
.header("content-length", "2");
if let Some(value) = connection {
response = response.header("connection", value);
}
let scenario = Scenario::builder()
.get("https://a.test")
.response(response.body(()).unwrap())
.recv_body("ok", false)
.build();
assert_eq!(
scenario.to_cleanup().must_close_connection(),
must_close,
"{connection:?}"
);
}
}
#[test]
fn http10_response_without_body_must_close() {
let scenario = Scenario::builder()
.head("https://a.test")
.response(
Response::builder()
.version(Version::HTTP_10)
.body(())
.unwrap(),
)
.build();
assert!(scenario.to_cleanup().must_close_connection());
}
#[test]
fn amended_connection_close_must_close() {
let scenario = Scenario::builder().get("https://a.test").build();
let mut call = scenario.to_prepare();
call.header("connection", "keep-alive, Close").unwrap();
let reasons = &call.inner().close_reason;
assert!(reasons.contains(&CloseReason::ClientConnectionClose));
}
#[test]
fn successful_connect_is_not_reusable() {
for status in [StatusCode::OK, StatusCode::NO_CONTENT] {
let scenario = Scenario::builder()
.connect("https://a.test")
.response(Response::builder().status(status).body(()).unwrap())
.build();
let call = scenario.to_cleanup();
assert!(call.must_close_connection());
let reasons = &call.inner().close_reason;
assert!(reasons.contains(&CloseReason::ConnectTunnel));
}
}
#[test]
fn unframed_redirect_is_close_delimited() {
let scenario = Scenario::builder()
.get("https://a.test")
.response(
Response::builder()
.status(StatusCode::FOUND)
.header("location", "/next")
.body(())
.unwrap(),
)
.build();
let mut body = scenario.to_recv_body();
assert_eq!(body.body_mode(), crate::BodyMode::CloseDelimited);
let mut output = [0; 16];
let (consumed, written) = body.read(b"redirect body", &mut output).unwrap();
assert_eq!(consumed, 13);
assert_eq!(&output[..written], b"redirect body");
let crate::client::RecvBodyResult::Redirect(call) = body.proceed().unwrap() else {
panic!("expected redirect")
};
assert!(call.must_close_connection());
}
#[test]
fn close_due_to_client_connection_close() {
let scenario = Scenario::builder()
.get("https://a.test")
.header("connection", "close")
.build();
let call = scenario.to_cleanup();
let inner = call.inner();
assert_eq!(
*inner.close_reason.first().unwrap(),
CloseReason::ClientConnectionClose
);
assert!(call.must_close_connection());
}
#[test]
fn close_due_to_server_connection_close() {
let scenario = Scenario::builder()
.get("https://a.test")
.response(
Response::builder()
.header("connection", "close")
.body(())
.unwrap(),
)
.build();
let call = scenario.to_cleanup();
let inner = call.inner();
assert_eq!(
*inner.close_reason.first().unwrap(),
CloseReason::ServerConnectionClose
);
assert!(call.must_close_connection());
}
#[test]
fn close_due_to_not_100_continue() {
let scenario = Scenario::builder()
.post("https://q.test")
.header("expect", "100-continue")
.send_body("hi", false)
.build();
let mut call = scenario.to_await_100();
let input = write_response(
&Response::builder()
.status(StatusCode::FORBIDDEN)
.body(())
.unwrap(),
);
call.try_read_100(&input).unwrap();
let inner = call.inner();
assert_eq!(
*inner.close_reason.first().unwrap(),
CloseReason::Not100Continue
);
}
#[test]
fn close_due_to_close_delimited_body() {
let scenario = Scenario::builder().get("https://a.test").build();
let call = scenario.to_cleanup();
let inner = call.inner();
assert_eq!(
*inner.close_reason.first().unwrap(),
CloseReason::CloseDelimitedBody
);
assert!(call.must_close_connection());
}
#[test]
fn close_due_to_server_connection_close_capitalized() {
let scenario = Scenario::builder()
.get("https://a.test")
.response(
Response::builder()
.header("connection", "Close")
.body(())
.unwrap(),
)
.build();
let call = scenario.to_cleanup();
let inner = call.inner();
assert!(
inner
.close_reason
.contains(&CloseReason::ServerConnectionClose)
);
assert!(call.must_close_connection());
}
#[test]
fn close_due_to_server_connection_close_in_list() {
let scenario = Scenario::builder()
.get("https://a.test")
.response(
Response::builder()
.header("connection", "keep-alive, close")
.body(())
.unwrap(),
)
.build();
let call = scenario.to_cleanup();
let inner = call.inner();
assert!(
inner
.close_reason
.contains(&CloseReason::ServerConnectionClose)
);
assert!(call.must_close_connection());
}
#[test]
fn close_due_to_client_connection_close_capitalized() {
let scenario = Scenario::builder()
.get("https://a.test")
.header("connection", "Close")
.build();
let call = scenario.to_cleanup();
let inner = call.inner();
assert!(
inner
.close_reason
.contains(&CloseReason::ClientConnectionClose)
);
assert!(call.must_close_connection());
}