#![cfg(feature = "testing")]
use std::time::Duration;
use futures::StreamExt;
use futures::future::BoxFuture;
use ruststream::testing::InProcess;
use ruststream::{
IncomingMessage, OutgoingMessage, PublishPolicy, Publisher, Subscriber, SubscriptionSource,
};
use ruststream_fred::{
ConnectedRedisBroker, RedisBroker, RedisList, RedisListPublish, RedisPubSub,
RedisPubSubPublish, RedisStream,
};
const URL: &str = "redis://localhost:6379";
const WAIT: Duration = Duration::from_secs(1);
const QUIET: Duration = Duration::from_millis(50);
async fn connected() -> ConnectedRedisBroker {
RedisBroker::standalone(URL)
.connect_in_process()
.await
.expect("connect")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_stream_publish_to_a_key_a_list_reads_breaks_the_list_read() {
let broker = connected().await;
let mut jobs = RedisList::new("jobs")
.reliable()
.subscribe(&broker)
.await
.expect("subscribe");
broker
.publisher()
.publish(OutgoingMessage::new("jobs", b"x"), None)
.await
.expect("XADD creates a stream under a name nothing holds yet");
let mut stream = Box::pin(jobs.stream());
let err = tokio::time::timeout(WAIT, stream.next())
.await
.expect("the read answers within the wait")
.expect("the stream has a next item")
.expect_err("a pop on a stream key fails");
assert!(err.to_string().contains("WRONGTYPE"), "got {err}");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_list_publish_to_a_key_a_stream_reads_is_refused() {
let broker = connected().await;
let _orders = RedisStream::new("orders")
.group("workers")
.subscribe(&broker)
.await
.expect("subscribe");
let lists = RedisListPublish::new().pair(&broker).await.expect("pair");
let err = lists
.publish(OutgoingMessage::new("orders", b"x"), None)
.await
.expect_err("LPUSH on a stream key is WRONGTYPE on a server");
assert!(err.to_string().contains("WRONGTYPE"), "got {err}");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_stream_publish_to_a_name_a_channel_reads_reaches_no_subscriber() {
let broker = connected().await;
let mut events = RedisPubSub::new("events")
.subscribe(&broker)
.await
.expect("subscribe");
broker
.publisher()
.publish(OutgoingMessage::new("events", b"x"), None)
.await
.expect("an XADD under a channel's name is a stream write");
let mut stream = Box::pin(events.stream());
assert!(
tokio::time::timeout(QUIET, stream.next()).await.is_err(),
"a stream write is not a channel message"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_channel_publish_to_a_key_a_list_reads_reaches_no_list() {
let broker = connected().await;
let mut jobs = RedisList::new("jobs")
.subscribe(&broker)
.await
.expect("subscribe");
let channels = RedisPubSubPublish::new().pair(&broker).await.expect("pair");
channels
.publish(OutgoingMessage::new("jobs", b"x"), None)
.await
.expect("a PUBLISH with no subscriber is taken and delivered to nobody");
let mut stream = Box::pin(jobs.stream());
assert!(
tokio::time::timeout(QUIET, stream.next()).await.is_err(),
"a channel message is not a list element"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_key_keeps_the_type_its_first_write_gave_it() {
let broker = connected().await;
broker
.publisher()
.publish(OutgoingMessage::new("audit", b"x"), None)
.await
.expect("XADD creates the stream");
let lists = RedisListPublish::new().pair(&broker).await.expect("pair");
let err = lists
.publish(OutgoingMessage::new("audit", b"y"), None)
.await
.expect_err("LPUSH on the stream the XADD created is WRONGTYPE");
assert!(err.to_string().contains("WRONGTYPE"), "got {err}");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn each_form_is_reached_by_its_own_publish() {
let broker = connected().await;
let mut orders = RedisStream::new("orders")
.group("workers")
.subscribe(&broker)
.await
.expect("subscribe stream");
let mut jobs = RedisList::new("jobs")
.reliable()
.subscribe(&broker)
.await
.expect("subscribe list");
let mut events = RedisPubSub::new("events")
.subscribe(&broker)
.await
.expect("subscribe channel");
broker
.publisher()
.publish(OutgoingMessage::new("orders", b"o"), None)
.await
.expect("XADD");
RedisListPublish::new()
.pair(&broker)
.await
.expect("pair")
.publish(OutgoingMessage::new("jobs", b"j"), None)
.await
.expect("LPUSH");
RedisPubSubPublish::new()
.pair(&broker)
.await
.expect("pair")
.publish(OutgoingMessage::new("events", b"e"), None)
.await
.expect("PUBLISH");
for (subscription, expected) in [
(&mut orders as &mut dyn NextPayload, b"o"),
(&mut jobs, b"j"),
(&mut events, b"e"),
] {
assert_eq!(subscription.next_payload().await, expected);
}
}
trait NextPayload {
fn next_payload(&mut self) -> BoxFuture<'_, Vec<u8>>;
}
impl<S> NextPayload for S
where
S: Subscriber + Send,
S::Message: Send,
{
fn next_payload(&mut self) -> BoxFuture<'_, Vec<u8>> {
Box::pin(async move {
let mut stream = Box::pin(self.stream());
let delivered = tokio::time::timeout(WAIT, stream.next())
.await
.expect("a delivery within the wait")
.expect("the stream has a next item")
.unwrap_or_else(|_| panic!("the delivery failed"));
delivered.payload().to_vec()
})
}
}