#![cfg(all(
feature = "testing",
feature = "memory",
feature = "json",
feature = "macros"
))]
use std::error::Error;
use ruststream::memory::{
MemoryBroker, MemoryError, MemoryPattern, MemorySource, PatternError, Routing,
};
use ruststream::runtime::{AppInfo, HandlerOutcome, RustStream};
use ruststream::testing::{TestApp, TestError};
use ruststream::{Outgoing, subscriber};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Outgoing, Serialize, Deserialize)]
struct Order {
id: u64,
}
#[subscriber("orders.eu")]
async fn europe(order: &Order) -> HandlerOutcome {
let _ = order.id;
HandlerOutcome::ack()
}
#[subscriber(MemoryPattern::new("orders.*"))]
async fn regions(order: &Order) -> HandlerOutcome {
let _ = order.id;
HandlerOutcome::ack()
}
fn app(routing: Routing) -> RustStream {
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(
MemoryBroker::new().routing(routing),
|b| {
b.include(europe);
b.include(regions);
},
)
}
async fn publish_both(tb: &TestApp<()>) -> Result<(), Box<dyn Error>> {
tb.broker::<MemoryBroker>()
.message(&Order { id: 1 })
.to("orders.eu")
.publish()
.await?;
tb.broker::<MemoryBroker>()
.message(&Order { id: 2 })
.to("orders.us")
.publish()
.await?;
tb.settle().await?;
Ok(())
}
async fn every_match_reaches_the_name_and_the_pattern(
tb: TestApp<()>,
) -> Result<(), Box<dyn Error>> {
publish_both(&tb).await?;
tb.broker::<MemoryBroker>()
.subscriber("orders.eu")
.assert_called_once()
.with(&Order { id: 1 });
assert_eq!(
tb.broker::<MemoryBroker>()
.subscriber("orders.*")
.received::<Order>(),
[Order { id: 1 }, Order { id: 2 }]
);
tb.shutdown().await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn every_match_is_the_default() -> Result<(), Box<dyn Error>> {
let app =
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(europe);
b.include(regions);
});
every_match_reaches_the_name_and_the_pattern(TestApp::start(app).await?).await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn every_match_live() -> Result<(), Box<dyn Error>> {
every_match_reaches_the_name_and_the_pattern(
TestApp::start_live(app(Routing::EveryMatch)).await?,
)
.await
}
async fn most_specific_leaves_the_pattern_the_rest(tb: TestApp<()>) -> Result<(), Box<dyn Error>> {
publish_both(&tb).await?;
tb.broker::<MemoryBroker>()
.subscriber("orders.eu")
.assert_called_once()
.with(&Order { id: 1 });
tb.broker::<MemoryBroker>()
.subscriber("orders.*")
.assert_called_once()
.with(&Order { id: 2 });
tb.shutdown().await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn most_specific_in_process() -> Result<(), Box<dyn Error>> {
most_specific_leaves_the_pattern_the_rest(TestApp::start(app(Routing::MostSpecific)).await?)
.await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn most_specific_live() -> Result<(), Box<dyn Error>> {
most_specific_leaves_the_pattern_the_rest(
TestApp::start_live(app(Routing::MostSpecific)).await?,
)
.await
}
#[subscriber(MemoryPattern::new("orders.>.eu"))]
async fn misplaced_tail(order: &Order) -> HandlerOutcome {
let _ = order.id;
HandlerOutcome::ack()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_bad_pattern_refuses_to_start() {
let app =
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(misplaced_tail);
});
let Err(TestError::Subscribe(err)) = TestApp::start(app).await else {
panic!("a bad pattern must refuse to start");
};
assert_eq!(
err.downcast_ref::<MemoryError>(),
Some(&MemoryError::InvalidPattern {
pattern: "orders.>.eu".to_owned(),
reason: PatternError::TailNotLast { token: 1 },
})
);
assert_eq!(
err.to_string(),
"subscription pattern \"orders.>.eu\" is invalid: `>` at token 1 is not the last token; \
it matches the rest of a name"
);
}
#[subscriber(MemorySource::new("orders.*"))]
async fn wildcard_by_name(order: &Order) -> HandlerOutcome {
let _ = order.id;
HandlerOutcome::ack()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_wildcard_in_a_name_refuses_to_start() {
let app =
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(wildcard_by_name);
});
let Err(TestError::Subscribe(err)) = TestApp::start(app).await else {
panic!("a wildcard name must refuse to start");
};
assert_eq!(
err.to_string(),
"subscription \"orders.*\" has a wildcard token and reads one name only; subscribe to a \
pattern with `MemoryPattern::new(\"orders.*\")`"
);
}