#![cfg(all(
feature = "testing",
feature = "memory",
feature = "json",
feature = "macros"
))]
use std::future::{Future, ready};
use ruststream::conformance::harness::{self, InProcessBroker};
use ruststream::memory::{ConnectedMemoryBroker, MemoryBroker, MemorySource, Retaining, Retention};
use ruststream::runtime::{App, AppInfo, HandlerOutcome, RustStream};
use ruststream::testing::{InProcess, TestApp, TestError};
use ruststream::{Broker, Outgoing, nonzero, register_testable_broker, subscriber};
use serde::{Deserialize, Serialize};
#[derive(Debug)]
struct Dialling {
bus: MemoryBroker<Retaining>,
}
impl Default for Dialling {
fn default() -> Self {
Self {
bus: MemoryBroker::retaining(Retention::Messages(nonzero!(64))),
}
}
}
#[derive(Debug, thiserror::Error)]
enum DialError {
#[error("no server to dial")]
NoServer,
}
impl Broker for Dialling {
type Error = DialError;
type Connected = ConnectedMemoryBroker<Retaining>;
fn connect(self) -> impl Future<Output = Result<Self::Connected, Self::Error>> + Send {
ready(Err(DialError::NoServer))
}
}
impl InProcess for Dialling {
async fn connect_in_process(self) -> Result<Self::Connected, Self::Error> {
Ok(self
.bus
.connect()
.await
.expect("the in-memory bus connects without I/O"))
}
}
register_testable_broker!(Dialling);
#[derive(Debug, Outgoing, Serialize, Deserialize, PartialEq)]
#[outgoing(name = "orders")]
struct Order {
id: u64,
}
#[subscriber("orders")]
async fn accept(order: &Order) -> HandlerOutcome {
let _ = order.id;
HandlerOutcome::ack()
}
fn app() -> impl App {
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(Dialling::default(), |b| {
b.include(accept);
})
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_harness_runs_the_production_app_through_the_in_process_transition() {
let tb = TestApp::start(app()).await.expect("start");
tb.broker::<Dialling>()
.message(&Order { id: 1 })
.publish()
.await
.expect("publish");
tb.broker::<Dialling>()
.subscriber("orders")
.assert_called_once()
.with(&Order { id: 1 })
.settled(HandlerOutcome::ack());
tb.shutdown().await.expect("shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_routing_suite_connects_the_broker_in_process() {
harness::run_suite(Dialling::default).await;
}
#[allow(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_ladder_suite_runs_in_process_through_the_adapter() {
harness::lifecycle(
|| InProcessBroker::new(Dialling::default()),
|name| MemorySource::new(name),
|connected| connected.publisher(),
)
.await;
}
#[derive(Debug, Default)]
struct Misconfigured;
impl Broker for Misconfigured {
type Error = DialError;
type Connected = ConnectedMemoryBroker;
fn connect(self) -> impl Future<Output = Result<Self::Connected, Self::Error>> + Send {
ready(Err(DialError::NoServer))
}
}
impl InProcess for Misconfigured {
fn connect_in_process(
self,
) -> impl Future<Output = Result<Self::Connected, Self::Error>> + Send {
ready(Err(DialError::NoServer))
}
}
register_testable_broker!(Misconfigured);
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_failed_in_process_transition_is_reported_with_the_broker() {
let app = RustStream::new(AppInfo::new("orders", "0.1.0")).register_broker(Misconfigured);
match TestApp::start(app).await {
Err(TestError::Connect { broker, source }) => {
assert!(broker.ends_with("Misconfigured"), "{broker}");
assert_eq!(source.to_string(), "no server to dial");
}
other => panic!("expected a failed transition, got {:?}", other.map(|_| ())),
}
}