#![cfg(feature = "testing")]
use ruststream::HeaderMap;
use ruststream::prelude::*;
use ruststream::testing::{Outcome, TestApp};
use ruststream_zeromq::{ZmqEndpoint, ZmqQueue, ZmqQueuePublish};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, Outgoing)]
struct Draft {
header: String,
value: String,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, Outgoing)]
#[outgoing(name = "notes")]
struct Note {
header: String,
value: String,
}
#[derive(OutSlot)]
#[publishes(Note)]
struct Notes;
#[subscriber("drafts")]
async fn pass_on(draft: &Draft, Out(notes): Out<impl Publisher, Notes>) -> HandlerOutcome {
let mut headers = HeaderMap::new();
headers.insert(draft.header.clone(), draft.value.clone());
let note = Note {
header: draft.header.clone(),
value: draft.value.clone(),
};
match notes.message(¬e).with_headers(headers).publish().await {
Ok(()) => HandlerOutcome::ack(),
Err(_) => HandlerOutcome::drop(),
}
}
#[subscriber("notes")]
async fn take_note(note: &Note, ctx: &mut Context) -> HandlerOutcome {
if ctx.headers().get_str(¬e.header) == Some(note.value.as_str()) {
HandlerOutcome::ack()
} else {
HandlerOutcome::drop()
}
}
fn notes_app() -> RustStream {
let notes = ZmqQueue::new(ZmqEndpoint::bind("tcp://127.0.0.1:0")).bindable();
let to_notes = notes.bind(ZmqQueuePublish);
RustStream::new(AppInfo::new("zmq-header-values", "0.0.0"))
.with_broker_labeled("notes", notes, |b| {
b.include(take_note);
})
.with_broker_labeled(
"relay",
ZmqQueue::new(ZmqEndpoint::bind("tcp://127.0.0.1:0")),
|b| {
b.include(pass_on).out(Notes, to_notes).build();
},
)
}
fn draft(header: &str, value: &str) -> Draft {
Draft {
header: header.to_owned(),
value: value.to_owned(),
}
}
fn unwritable_drafts() -> Vec<Draft> {
vec![
draft("x-note", "first\nsecond"),
draft("x-tenant", "acme\nx-ruststream-retry-count: 9"),
draft("x-note", "first\rsecond"),
draft("x-note", " padded"),
draft("x-note", "padded "),
draft("x-a:b", "value"),
draft("x-a\nb", "value"),
draft(" x-note", "value"),
]
}
async fn every_unwritable_header_is_refused(tb: TestApp<()>) {
let drafts = unwritable_drafts();
for draft in &drafts {
tb.broker_named("relay")
.message(draft)
.to("drafts")
.publish()
.await
.expect("the draft reaches the relay");
}
tb.settle().await.expect("every draft is handled");
let outcomes = tb.broker_named("relay").subscriber("drafts").outcomes();
assert_eq!(
outcomes.len(),
drafts.len(),
"every draft reaches the relay"
);
assert!(
outcomes.iter().all(|outcome| *outcome == Outcome::Drop),
"the publish of a header the frame cannot carry must fail, got {outcomes:?}",
);
tb.broker_named("notes")
.subscriber("notes")
.assert_not_called();
tb.shutdown().await.expect("the service stops");
}
async fn a_writable_value_arrives_unchanged(tb: TestApp<()>) {
let draft = draft("x-note", "a: b c");
tb.broker_named("relay")
.message(&draft)
.to("drafts")
.publish()
.await
.expect("the draft reaches the relay");
tb.settle()
.await
.expect("the note is passed on and handled");
tb.broker_named("relay")
.subscriber("drafts")
.assert_called_once()
.settled(HandlerOutcome::ack());
tb.broker_named("notes")
.subscriber("notes")
.assert_called_once()
.with(&Note {
header: draft.header,
value: draft.value,
})
.settled(HandlerOutcome::ack());
tb.shutdown().await.expect("the service stops");
}
#[tokio::test(start_paused = true)]
async fn every_unwritable_header_is_refused_in_process() {
let tb = TestApp::start(notes_app())
.await
.expect("the harness starts");
every_unwritable_header_is_refused(tb).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn every_unwritable_header_is_refused_live() {
let tb = TestApp::start_live(notes_app())
.await
.expect("the harness starts over loopback sockets");
every_unwritable_header_is_refused(tb).await;
}
#[tokio::test(start_paused = true)]
async fn a_writable_value_arrives_unchanged_in_process() {
let tb = TestApp::start(notes_app())
.await
.expect("the harness starts");
a_writable_value_arrives_unchanged(tb).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_writable_value_arrives_unchanged_live() {
let tb = TestApp::start_live(notes_app())
.await
.expect("the harness starts over loopback sockets");
a_writable_value_arrives_unchanged(tb).await;
}