#![cfg(all(
feature = "memory",
feature = "macros",
feature = "json",
feature = "testing"
))]
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
#[derive(Deserialized)]
struct Frame<'a>(&'a [u8]);
#[derive(
Debug, Outgoing, serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq,
)]
struct Report {
len: usize,
}
#[derive(Outgoing, Serialized)]
struct Wire(Vec<u8>);
#[subscriber("lanes.measure", reply("lanes.measure.out"))]
async fn measure(frame: &Frame<'_>) -> Report {
Report { len: frame.0.len() }
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_raw_input_answers_with_an_encoded_reply() {
let app =
RustStream::new(AppInfo::new("lanes", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(measure);
});
let tb = TestApp::start(app).await.expect("harness start");
tb.broker::<MemoryBroker>()
.message(&Wire(b"four".to_vec()))
.to("lanes.measure")
.publish()
.await
.expect("publish");
tb.broker::<MemoryBroker>()
.published::<Report>("lanes.measure.out")
.assert_called_once()
.with(&Report { len: 4 });
}
#[derive(Outgoing, Serialized)]
#[outgoing(name = "lanes.exports")]
struct WireExport(Vec<u8>);
#[derive(OutSlot)]
#[publishes(WireExport)]
struct Exports;
#[subscriber("lanes.chunks")]
async fn export(frame: &Frame<'_>, Out(out): Out<impl Publisher, Exports>) -> HandlerOutcome {
let wire = WireExport(frame.0.to_vec());
if out.message(&wire).publish().await.is_err() {
return HandlerOutcome::retry();
}
HandlerOutcome::ack()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_serialized_out_type_is_a_dictionary_member() {
let broker = MemoryBroker::new();
let ingress = broker.publisher();
let app = RustStream::new(AppInfo::new("exports", "0.1.0")).with_broker(broker, |b| {
b.include(export).out(Exports, Publish).build();
});
let tb = TestApp::start(app).await.expect("harness start");
ingress
.message(&Wire(b"chunk".to_vec()))
.to("lanes.chunks")
.publish()
.await
.expect("publish");
tb.settle().await.expect("settle");
tb.broker::<MemoryBroker>()
.published::<Vec<u8>>("lanes.exports")
.assert_called_once()
.with_raw(b"chunk");
tb.shutdown().await.expect("graceful shutdown");
}
#[subscriber("lanes.exports")]
async fn absorb(frame: &Frame<'_>) -> HandlerOutcome {
let _ = frame.0.len();
HandlerOutcome::ack()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_test_client_injects_a_serialized_value_verbatim() {
let app =
RustStream::new(AppInfo::new("exports", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(absorb);
});
let tb = TestApp::start(app).await.expect("harness start");
tb.broker::<MemoryBroker>()
.message(&WireExport(vec![0xde, 0xad, 0xbe, 0xef]))
.publish()
.await
.expect("inject");
tb.broker::<MemoryBroker>()
.subscriber("lanes.exports")
.assert_called_once()
.with_raw(b"\xde\xad\xbe\xef")
.settled(HandlerOutcome::ack());
tb.shutdown().await.expect("graceful shutdown");
}
#[cfg(feature = "asyncapi")]
#[test]
fn the_typed_form_keeps_the_serialized_metadata() {
use ruststream::asyncapi::build_spec;
let app =
RustStream::new(AppInfo::new("exports", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(export).out(Exports, Publish).build();
});
let spec = build_spec(&app);
let message = &spec.components.messages["WireExport"];
assert!(
message.payload.is_none(),
"a serialized wire has no serde model to report"
);
assert!(spec.channels.contains_key("lanes.exports"));
assert!(
spec.messages_without_schema().is_empty(),
"the serialized wire is schema-free by design, not a gap"
);
}