use crate::{
ClientError, ErrorKind, Result,
client::PubSubMessage,
resp::{RespBuf, RespFrameParser, RespResponse, RespTapeMut},
};
use bytes::Bytes;
fn response(buf: &[u8]) -> Result<RespResponse> {
let mut tape = RespTapeMut::default();
let (frame, len) = RespFrameParser::new(buf, &mut tape).parse()?;
Ok(RespResponse::new(
RespBuf::from(Bytes::copy_from_slice(&buf[..len])),
frame,
))
}
fn push(kind: &str, parts: &[&[u8]]) -> Vec<u8> {
let mut buf = format!(">{}\r\n${}\r\n{kind}\r\n", parts.len() + 1, kind.len()).into_bytes();
for part in parts {
buf.extend_from_slice(format!("${}\r\n", part.len()).as_bytes());
buf.extend_from_slice(part);
buf.extend_from_slice(b"\r\n");
}
buf
}
fn convert(buf: &[u8]) -> Result<PubSubMessage> {
PubSubMessage::try_from(&response(buf)?)
}
#[test]
fn message_carries_no_pattern() -> Result<()> {
let message = convert(&push("message", &[b"mychannel", b"mymessage"]))?;
assert_eq!(b"", message.pattern());
assert_eq!(b"mychannel", message.channel());
assert_eq!(b"mymessage", message.payload());
Ok(())
}
#[test]
fn smessage_carries_no_pattern() -> Result<()> {
let message = convert(&push("smessage", &[b"mychannel", b"mymessage"]))?;
assert_eq!(b"", message.pattern());
assert_eq!(b"mychannel", message.channel());
assert_eq!(b"mymessage", message.payload());
Ok(())
}
#[test]
fn pmessage_keeps_its_three_segments_apart() -> Result<()> {
let message = convert(&push(
"pmessage",
&[b"mychannel*", b"mychannel11", b"mymessage"],
))?;
assert_eq!(b"mychannel*", message.pattern());
assert_eq!(b"mychannel11", message.channel());
assert_eq!(b"mymessage", message.payload());
Ok(())
}
#[test]
fn empty_segments_stay_empty_and_distinct() -> Result<()> {
let message = convert(&push("message", &[b"mychannel", b""]))?;
assert_eq!(b"", message.pattern());
assert_eq!(b"mychannel", message.channel());
assert_eq!(b"", message.payload());
let message = convert(&push("pmessage", &[b"*", b"", b""]))?;
assert_eq!(b"*", message.pattern());
assert_eq!(b"", message.channel());
assert_eq!(b"", message.payload());
Ok(())
}
#[test]
fn a_payload_of_any_size_reads_back_whole() -> Result<()> {
for size in [1usize, 63, 64, 65, 1024, 64 * 1024] {
let payload = vec![b'x'; size];
let message = convert(&push("message", &[b"mychannel", &payload]))?;
assert_eq!(b"", message.pattern());
assert_eq!(b"mychannel", message.channel());
assert_eq!(size, message.payload().len());
assert_eq!(payload, message.payload());
}
Ok(())
}
#[test]
fn a_push_that_is_not_a_message_is_refused() -> Result<()> {
for buf in [
push("subscribe", &[b"mychannel", b"1"]),
push("unsubscribe", &[b"mychannel", b"0"]),
push("unknown", &[b"mychannel", b"mymessage"]),
b"*3\r\n$7\r\nmessage\r\n$9\r\nmychannel\r\n$9\r\nmymessage\r\n".to_vec(),
] {
let error = convert(&buf).unwrap_err();
assert!(
matches!(
error.kind(),
ErrorKind::Client(ClientError::UnexpectedPubSubMessage)
),
"expected UnexpectedPubSubMessage, got {error:?}"
);
}
Ok(())
}
#[test]
fn the_segments_read_back_as_text() -> Result<()> {
let message = convert(&push(
"pmessage",
&[b"mychannel*", b"mychannel11", b"mymessage"],
))?;
assert_eq!("mychannel*", message.pattern_str()?);
assert_eq!("mychannel11", message.channel_str()?);
assert_eq!("mymessage", message.payload_as::<&str>()?);
Ok(())
}
#[test]
fn a_segment_that_is_not_text_is_refused() -> Result<()> {
let message = convert(&push("message", &[b"\xff", b"\xff"]))?;
assert_eq!(b"\xff", message.channel());
for error in [
message.channel_str().unwrap_err(),
message.payload_as::<&str>().unwrap_err(),
message.payload_as::<String>().unwrap_err(),
] {
assert!(
matches!(error.kind(), ErrorKind::Utf8(_)),
"expected a UTF-8 error, got {error:?}"
);
}
Ok(())
}
#[test]
fn a_payload_is_read_as_a_rust_type() -> Result<()> {
let message = convert(&push("message", &[b"mychannel", b"42"]))?;
assert_eq!(42i64, message.payload_as::<i64>()?);
assert_eq!(42u8, message.payload_as::<u8>()?);
assert_eq!("42", message.payload_as::<String>()?);
let message = convert(&push("message", &[b"mychannel", b"1.5"]))?;
assert_eq!(1.5f64, message.payload_as::<f64>()?);
let message = convert(&push("message", &[b"mychannel", b"one"]))?;
assert!(message.payload_as::<i64>().is_err());
Ok(())
}
#[cfg(feature = "json")]
#[test]
fn a_json_payload_is_read_as_a_document() -> Result<()> {
use crate::resp::Json;
let message = convert(&push(
"message",
&[b"mychannel", br#"{"id":1,"name":"mike"}"#],
))?;
let Json(document): Json<serde_json::Value> = message.payload_as()?;
assert_eq!(1, document["id"]);
assert_eq!("mike", document["name"]);
Ok(())
}