mod common;
use std::time::Duration;
use common::Server;
use weida::{Error, TransferMeta};
const DEADLINE: Duration = Duration::from_secs(15);
async fn within<F: Future>(f: F) -> F::Output {
tokio::time::timeout(DEADLINE, f)
.await
.expect("operation timed out")
}
#[tokio::test]
async fn push_delivery_receipt() {
let server = Server::start().await;
let puller = server.listener.puller("/jobs").expect("puller");
let client = server.client_runtime();
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/jobs")))
.await
.expect("connect");
let payload = vec![0x5au8; 1024];
let mut transfer = within(pusher.open(TransferMeta::default()))
.await
.expect("open");
within(transfer.write_all(&payload)).await.expect("write");
let delivery = transfer.finish().expect("finish");
within(delivery.delivered()).await.expect("delivered");
let transfer = within(puller.recv()).await.expect("recv");
assert_eq!(transfer.meta().endpoint.as_deref(), Some("/jobs"));
assert_eq!(transfer.meta().topic, None);
let body = within(transfer.collect(64 * 1024)).await.expect("collect");
assert_eq!(body, payload);
client.shutdown().await;
}
#[tokio::test]
async fn push_best_effort() {
let server = Server::start().await;
let puller = server.listener.puller("/jobs").expect("puller");
let client = server.client_runtime();
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/jobs")))
.await
.expect("connect");
within(pusher.send(b"fire and forget")).await.expect("send");
let transfer = within(puller.recv()).await.expect("recv");
let body = within(transfer.collect(1024)).await.expect("collect");
assert_eq!(body, b"fire and forget");
client.shutdown().await;
}
#[tokio::test]
async fn push_round_robins_two_peers() {
let server = Server::start().await;
let left = server.listener.puller("/left").expect("left");
let right = server.listener.puller("/right").expect("right");
let client = server.client_runtime();
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/left")))
.await
.expect("dial left");
within(pusher.connect(&server.url("/right")))
.await
.expect("dial right");
assert_eq!(pusher.peer_count(), 2);
const TOTAL: usize = 8;
for i in 0..TOTAL {
within(pusher.send(format!("m{i}").as_bytes()))
.await
.expect("send");
}
let mut seen = Vec::new();
for _ in 0..TOTAL / 2 {
let t = within(left.recv()).await.expect("recv left");
assert_eq!(t.meta().endpoint.as_deref(), Some("/left"));
seen.push(within(t.collect(64)).await.expect("collect left"));
}
for _ in 0..TOTAL / 2 {
let t = within(right.recv()).await.expect("recv right");
assert_eq!(t.meta().endpoint.as_deref(), Some("/right"));
seen.push(within(t.collect(64)).await.expect("collect right"));
}
seen.sort();
let mut expected: Vec<Vec<u8>> = (0..TOTAL).map(|i| format!("m{i}").into_bytes()).collect();
expected.sort();
assert_eq!(seen, expected);
client.shutdown().await;
}
#[tokio::test]
async fn push_cancel_mid_transfer() {
let server = Server::start().await;
let puller = server.listener.puller("/jobs").expect("puller");
let client = server.client_runtime();
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/jobs")))
.await
.expect("connect");
let mut transfer = within(pusher.open(TransferMeta::default()))
.await
.expect("open");
within(transfer.write_all(b"partial")).await.expect("write");
let mut inbound = within(puller.recv()).await.expect("recv");
transfer.cancel();
let err = within(inbound.read_capped(64 * 1024))
.await
.expect_err("a canceled transfer must not read as complete");
assert!(matches!(err, Error::Canceled), "{err:?}");
within(pusher.send(b"next")).await.expect("send");
let next = within(puller.recv()).await.expect("recv next");
assert_eq!(within(next.collect(64)).await.expect("collect"), b"next");
client.shutdown().await;
}
#[tokio::test]
async fn request_to_pull_path_is_unsupported() {
let server = Server::start().await;
let _puller = server.listener.puller("/jobs").expect("puller");
let client = server.client_runtime();
let requester = client.requester(server.trust());
within(requester.connect(&server.url("/jobs")))
.await
.expect("connect");
let err = within(requester.request(b"hello"))
.await
.expect_err("a request to a pull path must be refused");
assert!(matches!(err, Error::Unsupported), "{err:?}");
client.shutdown().await;
}
#[tokio::test]
async fn push_to_rep_path_is_unsupported() {
let server = Server::start().await;
let _replier = server.listener.replier("/rpc").expect("replier");
let client = server.client_runtime();
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/rpc")))
.await
.expect("connect");
assert!(
matches!(
within(push_and_confirm(&pusher, &beyond_the_window())).await,
Err(Error::Unsupported)
),
"a push to a replier path must be refused with Unsupported"
);
client.shutdown().await;
}
#[tokio::test]
async fn push_to_an_unknown_path_is_reported() {
let server = Server::start().await;
let _puller = server.listener.puller("/jobs").expect("puller");
let client = server.client_runtime();
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/nope")))
.await
.expect("connect");
assert!(
matches!(
within(push_and_confirm(&pusher, &beyond_the_window())).await,
Err(Error::UnknownEndpoint)
),
"an unknown path must be reported"
);
client.shutdown().await;
}
fn beyond_the_window() -> Vec<u8> {
vec![0u8; 2 * 1024 * 1024]
}
async fn push_and_confirm(pusher: &weida::Pusher, body: &[u8]) -> Result<(), Error> {
let mut transfer = pusher.open(TransferMeta::default()).await?;
transfer.write_all(body).await?;
transfer.finish()?.delivered().await
}