use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::io::{AsyncReadExt, duplex};
use super::*;
const PATIENCE: Duration = Duration::from_secs(5);
struct Refusing;
impl AsyncWrite for Refusing {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &[u8],
) -> Poll<std::io::Result<usize>> {
Poll::Ready(Err(std::io::Error::other("the backend stopped reading")))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Poll::Ready(Ok(()))
}
}
struct ShutdownRefused;
impl AsyncWrite for ShutdownRefused {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<std::io::Result<usize>> {
Poll::Ready(Ok(buf.len()))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Poll::Ready(Err(std::io::Error::from(std::io::ErrorKind::NotConnected)))
}
}
#[tokio::test]
async fn a_body_that_stops_short_is_charged_to_the_client_and_not_to_the_backend() {
let mut source: &[u8] = b"0123456789";
let mut from_client = Buffered::new(&mut source, Vec::new());
let mut to_backend: Vec<u8> = Vec::new();
let fault = send(&mut from_client, &mut to_backend, Framing::Length(1000)).await;
assert_eq!(fault, Some(Fault::Client));
assert_eq!(
to_backend, b"0123456789",
"the prefix that did arrive still goes upstream"
);
}
#[tokio::test]
async fn a_backend_that_stops_taking_bytes_is_charged_to_the_backend_and_not_to_the_client() {
let mut source: &[u8] = b"0123456789";
let mut from_client = Buffered::new(&mut source, Vec::new());
let fault = send(&mut from_client, &mut Refusing, Framing::Length(10)).await;
assert_eq!(fault, Some(Fault::Backend));
}
#[tokio::test]
async fn an_unreadable_chunk_size_is_charged_to_the_client() {
let mut source: &[u8] = b"zz\r\n";
let mut from_client = Buffered::new(&mut source, Vec::new());
let mut to_backend: Vec<u8> = Vec::new();
let fault = send(&mut from_client, &mut to_backend, Framing::Chunked).await;
assert_eq!(fault, Some(Fault::Client));
}
#[tokio::test]
async fn a_shutdown_that_fails_does_not_turn_a_client_fault_into_a_backend_one() {
let mut source: &[u8] = b"0123456789";
let mut from_client = Buffered::new(&mut source, Vec::new());
let fault = send(
&mut from_client,
&mut ShutdownRefused,
Framing::Length(1000),
)
.await;
assert_eq!(
fault,
Some(Fault::Client),
"the shutdown's own failure must not be read as the backend's"
);
}
#[tokio::test]
async fn an_unfinishable_body_half_closes_the_upstream_write() {
let (mut edge, mut backend) = duplex(64 * 1024);
let mut source: &[u8] = b"0123456789";
let mut from_client = Buffered::new(&mut source, Vec::new());
let fault = send(&mut from_client, &mut edge, Framing::Length(1000)).await;
assert_eq!(fault, Some(Fault::Client));
let mut seen = Vec::new();
tokio::time::timeout(PATIENCE, backend.read_to_end(&mut seen))
.await
.expect("the backend must be told the body stopped")
.expect("no transport failure");
assert_eq!(seen, b"0123456789");
}
#[tokio::test(start_paused = true)]
async fn a_body_that_arrives_whole_leaves_the_upstream_write_open() {
let (mut edge, mut backend) = duplex(64 * 1024);
let mut source: &[u8] = b"0123456789";
let mut from_client = Buffered::new(&mut source, Vec::new());
let fault = send(&mut from_client, &mut edge, Framing::Length(10)).await;
assert_eq!(fault, None, "a complete body is nobody's fault");
let mut arrived = [0u8; 10];
backend
.read_exact(&mut arrived)
.await
.expect("the whole body arrives");
assert_eq!(&arrived, b"0123456789");
let mut after = [0u8; 1];
let further = tokio::time::timeout(Duration::from_secs(30), backend.read(&mut after)).await;
assert!(
further.is_err(),
"a complete body must not be followed by an end-of-stream the client never sent"
);
}