#![cfg(all(
feature = "macros",
feature = "memory",
feature = "json",
feature = "testing"
))]
mod common;
use std::future::{Future, ready};
use common::{Order, Receipt, Wire};
use ruststream::memory::prelude::*;
use ruststream::memory::{ConnectedMemoryBroker, MemoryMessage, MemoryPublisher, MemorySubscriber};
use ruststream::runtime::{
HandlerMetadata, RouterHandlers, RustStreamError, subscriber as subscriber_def,
};
use ruststream::testing::TestApp;
use ruststream::{
AddressedCopies, PairError, RedeliveryAddress, RedeliveryAddressed, SubscriptionSource,
};
#[subscriber("brc-in", reply("brc-out"))]
async fn brc_relay(o: &Order) -> Receipt {
Receipt { id: o.id }
}
#[subscriber("brc-batch-in", reply("brc-out"))]
async fn brc_batch_relay(orders: &[Order]) -> Vec<Receipt> {
orders.iter().map(|o| Receipt { id: o.id }).collect()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn handle_route_dispatches_through_a_prebuilt_subscriber() {
let broker = MemoryBroker::new();
let subscriber = broker.subscribe("brc-handle");
let router = Router::<MemoryBroker>::new().handle(
subscriber,
|msg: &MemoryMessage, _ctx: &mut Context| {
let _ = msg.payload();
async move { HandlerOutcome::ack() }
},
HandlerMetadata::raw("brc-handle"),
);
let app = RustStream::new(AppInfo::new("brc", "0.1.0")).with_broker(broker, |b| {
b.include_router(router);
});
let tb = TestApp::start(app).await.expect("startup failed");
tb.message(&Wire::of(b"ping"))
.to("brc-handle")
.publish()
.await
.expect("publish");
tb.broker::<MemoryBroker>()
.subscriber("brc-handle")
.assert_called_once()
.with_raw(b"ping")
.settled(HandlerOutcome::ack());
}
struct MetaBatch;
impl Handle<[Order]> for MetaBatch {
fn handle(
&self,
_orders: &[Order],
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), Vec<HandlerOutcome>>> {
ready(Ok(()))
}
}
#[test]
fn every_route_kind_reports_its_metadata_in_registration_order() {
let broker = MemoryBroker::new();
let router = Router::<MemoryBroker>::new()
.handle(
broker.subscribe("brc-meta-handle"),
|_msg: &MemoryMessage, _ctx: &mut Context| async { HandlerOutcome::ack() },
HandlerMetadata::raw("brc-meta-handle"),
)
.include(
subscriber_def("brc-meta-batch", MetaBatch)
.batch(nonzero!(64))
.build(),
)
.include(brc_relay)
.out(Reply, Publish)
.build()
.include(brc_batch_relay.batch(nonzero!(64)))
.out(Reply, Publish)
.build();
let names: Vec<_> = router.handlers().into_iter().map(|m| m.name).collect();
assert_eq!(
names,
[
"brc-meta-handle",
"brc-meta-batch",
"brc-in",
"brc-batch-in"
]
);
let mut nested = Vec::new();
RouterHandlers::collect_handlers(&router, &mut nested);
let nested: Vec<_> = nested.into_iter().map(|m| m.name).collect();
assert_eq!(nested, names);
}
struct RefusedPublish;
impl PublishPolicy<ConnectedMemoryBroker> for RefusedPublish {
type Live = MemoryPublisher;
fn pair(
self,
_connected: &ConnectedMemoryBroker,
) -> impl Future<Output = Result<MemoryPublisher, PairError>> {
ready(Err(PairError::from_boxed(Box::from(
"the reply publisher was refused",
))))
}
}
#[derive(Clone)]
struct ClosedSource;
impl SubscriptionSource<ConnectedMemoryBroker> for ClosedSource {
type Subscriber = MemorySubscriber;
type Copies = AddressedCopies;
fn name(&self) -> &'static str {
"brc-closed"
}
fn subscribe(
self,
_connected: &ConnectedMemoryBroker,
) -> impl Future<Output = Result<MemorySubscriber, MemoryError>> {
ready(Err(MemoryError::ShutDown))
}
}
impl RedeliveryAddressed<ConnectedMemoryBroker> for ClosedSource {
fn redelivery_address(
&self,
_connected: &ConnectedMemoryBroker,
) -> impl Future<Output = Result<RedeliveryAddress, MemoryError>> + Send {
ready(Ok(RedeliveryAddress::new("brc-closed")))
}
}
#[subscriber(ClosedSource {}, reply("brc-out"))]
async fn brc_closed_relay(order: &Order) -> Receipt {
Receipt { id: order.id }
}
fn assert_subscribe_error(result: Result<impl Sized, RustStreamError>, expected: &str) {
match result {
Ok(_) => panic!("startup must fail"),
Err(err) => {
assert!(
matches!(err, RustStreamError::Subscribe(_)),
"expected a subscription failure, got {err:?}"
);
let rendered = format!("{err}");
assert!(rendered.contains(expected), "{rendered}");
}
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn publishing_route_reports_a_refused_reply_publisher() {
let router = Router::<MemoryBroker>::new()
.include(brc_relay)
.out(Reply, RefusedPublish)
.build();
let app =
RustStream::new(AppInfo::new("brc-pair", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include_router(router);
});
assert_subscribe_error(app.start().await, "the reply publisher was refused");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn batch_publishing_route_reports_a_refused_reply_publisher() {
let router = Router::<MemoryBroker>::new()
.include(brc_batch_relay.batch(nonzero!(64)))
.out(Reply, RefusedPublish)
.build();
let app = RustStream::new(AppInfo::new("brc-batch-pair", "0.1.0")).with_broker(
MemoryBroker::new(),
|b| {
b.include_router(router);
},
);
assert_subscribe_error(app.start().await, "the reply publisher was refused");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn publishing_route_reports_a_source_that_never_opens() {
let router = Router::<MemoryBroker>::new()
.include(brc_closed_relay)
.out(Reply, Publish)
.build();
let app = RustStream::new(AppInfo::new("brc-source", "0.1.0")).with_broker(
MemoryBroker::new(),
|b| {
b.include_router(router);
},
);
assert_subscribe_error(app.start().await, "the memory broker is shut down");
}