use std::{
net::TcpListener as StdTcpListener,
pin::Pin,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
};
use rstest::fixture;
pub use wireframe::testkit::{TestError, TestResult};
use wireframe::{
app::{Envelope, Packet, PacketParts},
correlation::CorrelatableFrame,
serializer::BincodeSerializer,
};
#[must_use]
pub fn unused_listener() -> TestResult<StdTcpListener> { Ok(StdTcpListener::bind("localhost:0")?) }
#[derive(bincode::Encode, bincode::BorrowDecode, PartialEq, Debug)]
pub struct Echo(pub u8);
#[derive(bincode::Encode, bincode::BorrowDecode, PartialEq, Debug, Clone)]
pub struct CommonTestEnvelope {
pub id: u32,
pub correlation_id: Option<u64>,
pub payload: Vec<u8>,
}
impl CommonTestEnvelope {
#[must_use]
pub fn new(id: u32, correlation_id: Option<u64>, payload: Vec<u8>) -> Self {
Self {
id,
correlation_id,
payload,
}
}
#[must_use]
pub fn with_payload(id: u32, payload: Vec<u8>) -> Self {
Self {
id,
correlation_id: None,
payload,
}
}
#[must_use]
pub fn with_id(id: u32) -> Self {
Self {
id,
correlation_id: None,
payload: Vec::new(),
}
}
}
impl CorrelatableFrame for CommonTestEnvelope {
fn correlation_id(&self) -> Option<u64> { self.correlation_id }
fn set_correlation_id(&mut self, correlation_id: Option<u64>) {
self.correlation_id = correlation_id;
}
}
impl Packet for CommonTestEnvelope {
fn id(&self) -> u32 { self.id }
fn into_parts(self) -> PacketParts {
PacketParts::new(self.id, self.correlation_id, self.payload)
}
fn from_parts(parts: PacketParts) -> Self {
Self {
id: parts.id(),
correlation_id: parts.correlation_id(),
payload: parts.into_payload(),
}
}
}
pub type TestApp = wireframe::app::WireframeApp<BincodeSerializer, (), Envelope>;
#[fixture]
pub fn factory() -> impl Fn() -> TestApp + Send + Sync + Clone + 'static {
fn build() -> TestApp { TestApp::default() }
build
}
pub type CommonTestApp = wireframe::app::WireframeApp<BincodeSerializer, (), CommonTestEnvelope>;
pub type CommonHandler = Arc<
dyn Fn(&CommonTestEnvelope) -> Pin<Box<dyn std::future::Future<Output = ()> + Send>>
+ Send
+ Sync,
>;
pub fn echo_handler(counter: &Arc<AtomicUsize>) -> CommonHandler {
let counter = counter.clone();
Arc::new(move |_: &CommonTestEnvelope| {
let c = counter.clone();
Box::pin(async move {
c.fetch_add(1, Ordering::SeqCst);
})
})
}
pub fn echo_app_factory(
counter: &Arc<AtomicUsize>,
) -> impl Fn() -> TestResult<CommonTestApp> + Send + Sync + Clone + 'static {
let handler = echo_handler(counter);
move || {
let app = CommonTestApp::new()?;
Ok(app.route(1, handler.clone())?)
}
}
#[cfg(test)]
mod tests {
use wireframe::{app::Packet, correlation::CorrelatableFrame};
use super::{CommonTestEnvelope, factory, unused_listener};
#[test]
fn unused_listener_binds_loopback() -> super::TestResult {
let listener = unused_listener()?;
let addr = listener.local_addr()?;
if !addr.ip().is_loopback() {
return Err(format!("expected loopback address, got {addr:?}").into());
}
if addr.port() == 0 {
return Err("expected a non-zero port".into());
}
Ok(())
}
#[test]
fn common_test_envelope_round_trips_parts() -> super::TestResult {
let mut env = CommonTestEnvelope::new(7, Some(12), vec![1, 2, 3]);
if env.correlation_id() != Some(12) {
return Err("expected correlation id to be set".into());
}
env.set_correlation_id(Some(99));
if env.correlation_id() != Some(99) {
return Err("expected correlation id to update".into());
}
let rebuilt = CommonTestEnvelope::from_parts(env.clone().into_parts());
if rebuilt != env {
return Err("expected envelope to round trip via parts".into());
}
let empty = CommonTestEnvelope::with_id(5);
if empty.id != 5 {
return Err("expected id to be set".into());
}
if empty.correlation_id.is_some() {
return Err("expected no correlation id".into());
}
if !empty.payload.is_empty() {
return Err("expected empty payload".into());
}
Ok(())
}
#[test]
fn factory_builds_default_app() -> super::TestResult {
let build = factory();
let app = build();
let _codec = app.length_codec();
Ok(())
}
}