#![cfg(all(
feature = "macros",
feature = "memory",
feature = "json",
feature = "testing"
))]
mod common;
use std::time::Duration;
use common::{Order, Receipt};
use futures::future::join_all;
use ruststream::Buffered;
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
#[subscriber("tx-in", publish("tx-out"), workers(2))]
async fn tx_confirm(orders: &[Order]) -> Vec<Receipt> {
orders.iter().map(|o| Receipt { id: o.id }).collect()
}
fn orders(count: u32) -> Vec<Order> {
(1..=count).map(|id| Order { id }).collect()
}
fn sorted_ids(receipts: &[Receipt]) -> Vec<u32> {
let mut ids: Vec<u32> = receipts.iter().map(|r| r.id).collect();
ids.sort_unstable();
ids
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn transactional_replies_compose_with_a_batch_pool() {
let app = RustStream::new(AppInfo::new("tx", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(tx_confirm.batch(nonzero!(4)))
.out(Reply, TransactionalPublish)
.transactional();
});
let tb = TestApp::start(app).await.expect("startup failed");
let input = orders(4);
for result in join_all(
input
.iter()
.map(|order| tb.message(order).to("tx-in").publish()),
)
.await
{
result.expect("publish");
}
let receipts: Vec<Receipt> = tb
.broker::<MemoryBroker>()
.published::<Receipt>("tx-out")
.decoded();
assert_eq!(
sorted_ids(&receipts),
[1, 2, 3, 4],
"every handled order must be confirmed exactly once",
);
}
#[subscriber(Buffered::<Name>::new(Name::new("buf-in"))
.max_wait(Duration::from_millis(10)), workers(2))]
async fn buffered_drain(orders: &[Order]) -> HandlerOutcome {
let _ = orders;
HandlerOutcome::ack()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn buffered_sources_compose_with_a_batch_pool() {
let app = RustStream::new(AppInfo::new("buf", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(buffered_drain.batch(nonzero!(2)));
});
let tb = TestApp::start(app).await.expect("startup failed");
let input = orders(6);
for result in join_all(
input
.iter()
.map(|order| tb.message(order).to("buf-in").publish()),
)
.await
{
result.expect("publish");
}
let batches: Vec<Vec<Order>> = tb.broker::<MemoryBroker>().subscriber("buf-in").batches();
let mut drained: Vec<u32> = batches.iter().flatten().map(|o| o.id).collect();
drained.sort_unstable();
assert_eq!(
drained,
[1, 2, 3, 4, 5, 6],
"every delivery must be drained"
);
assert!(
batches.iter().all(|batch| batch.len() <= 2),
"the size cap must close a batch before the pool does: {:?}",
batches.iter().map(Vec::len).collect::<Vec<_>>(),
);
}
#[subscriber("pub-in", publish("pub-out"), workers(3))]
async fn pooled_relay(o: &Order) -> Receipt {
Receipt { id: o.id }
}
#[subscriber("pub-out")]
async fn pooled_check(_r: &Receipt) -> HandlerOutcome {
HandlerOutcome::ack()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn publishing_replies_compose_with_a_worker_pool() {
let app = RustStream::new(AppInfo::new("pub", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(pooled_relay);
b.include(pooled_check);
});
let tb = TestApp::start(app).await.expect("startup failed");
let input = orders(4);
for result in join_all(
input
.iter()
.map(|order| tb.message(order).to("pub-in").publish()),
)
.await
{
result.expect("publish");
}
let replied: Vec<Receipt> = tb.broker::<MemoryBroker>().subscriber("pub-out").received();
assert_eq!(
sorted_ids(&replied),
[1, 2, 3, 4],
"every delivery's reply must arrive",
);
}