#![cfg(all(
feature = "testing",
feature = "memory",
feature = "json",
feature = "macros"
))]
use std::error::Error;
use std::time::Duration;
use ruststream::memory::{MemoryBroker, MemoryPublish, MemorySource};
use ruststream::runtime::{AppInfo, HandlerOutcome, Out, RustStream, SubscriberSettings};
use ruststream::testing::TestApp;
use ruststream::{Broker, Buffered, OutSlot, Outgoing, Publisher, nonzero, subscriber};
use serde::{Deserialize, Serialize};
const HOLD: Duration = Duration::from_millis(300);
#[derive(Debug, Outgoing, Serialize, Deserialize, PartialEq)]
#[outgoing(name = "orders")]
struct Order {
id: u64,
}
#[subscriber("orders")]
async fn take_order(order: &Order) -> HandlerOutcome {
let _ = order.id;
HandlerOutcome::ack()
}
#[subscriber(Buffered::<MemorySource>::new(MemorySource::new("orders")).max_wait(HOLD))]
async fn take_orders_in_pairs(orders: &[Order]) -> HandlerOutcome {
let _ = orders;
HandlerOutcome::ack()
}
async fn start(app: RustStream, live: bool) -> Result<TestApp<()>, Box<dyn Error>> {
Ok(if live {
TestApp::start_live(app).await?
} else {
TestApp::start(app).await?
})
}
fn namesakes_app() -> RustStream {
RustStream::new(AppInfo::new("namesakes", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(take_order);
b.include(take_orders_in_pairs.batch(nonzero!(2)));
})
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_publish_owed_to_two_namesakes_settles_once_both_handled_it() -> Result<(), Box<dyn Error>>
{
for live in [false, true] {
let tb = start(namesakes_app(), live).await?;
tb.broker::<MemoryBroker>()
.message(&Order { id: 1 })
.publish()
.await?;
let seen: Vec<Order> = tb.broker::<MemoryBroker>().subscriber("orders").received();
assert_eq!(
seen,
[Order { id: 1 }, Order { id: 1 }],
"the settle returned before both subscriptions handled the order (live: {live})",
);
tb.shutdown().await?;
}
Ok(())
}
#[derive(OutSlot)]
#[publishes(Order)]
struct Relayed;
#[derive(Debug, Outgoing, Serialize, Deserialize)]
#[outgoing(name = "inbox")]
struct Job {
id: u64,
}
#[subscriber("inbox")]
async fn relay(job: &Job, Out(out): Out<impl Publisher, Relayed>) -> HandlerOutcome {
if out.message(&Order { id: job.id }).publish().await.is_err() {
return HandlerOutcome::retry();
}
HandlerOutcome::ack()
}
fn labeled_app() -> RustStream {
let east = MemoryBroker::new().bindable();
let west = MemoryBroker::new().bindable();
let to_east = east.bind(MemoryPublish);
let to_west = west.bind(MemoryPublish);
RustStream::new(AppInfo::new("labeled", "0.1.0"))
.with_broker_labeled("east", east, |b| {
b.include(take_orders_in_pairs.batch(nonzero!(2)));
b.include(relay).out(Relayed, to_west).build();
})
.with_broker_labeled("west", west, |b| {
b.include(take_order);
b.include(relay).out(Relayed, to_east).build();
})
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn labeled_brokers_of_one_type_are_counted_apart() -> Result<(), Box<dyn Error>> {
for live in [false, true] {
let tb = start(labeled_app(), live).await?;
tb.broker_named("west")
.message(&Order { id: 1 })
.publish()
.await?;
tb.broker_named("west")
.subscriber("orders")
.assert_called_once()
.with(&Order { id: 1 });
tb.broker_named("east")
.subscriber("orders")
.assert_not_called();
tb.broker_named("east")
.published::<Order>("orders")
.assert_not_called();
tb.broker_named("east")
.message(&Order { id: 2 })
.publish()
.await?;
tb.broker_named("east")
.subscriber("orders")
.assert_called_once()
.with(&Order { id: 2 });
tb.broker_named("west")
.subscriber("orders")
.assert_called_once();
tb.broker_named("east")
.published::<Order>("orders")
.assert_called_once();
tb.broker_named("west")
.message(&Job { id: 3 })
.publish()
.await?;
tb.broker_named("east")
.subscriber("orders")
.assert_called(2);
tb.broker_named("east")
.published::<Order>("orders")
.assert_called(2);
tb.broker_named("west")
.published::<Order>("orders")
.assert_called_once();
tb.shutdown().await?;
}
Ok(())
}