use std::error::Error;
use std::net::SocketAddr;
use std::time::{Duration, Instant};
use liminal_sdk::remote::websocket::WebSocketSubscriptionStream;
use liminal_sdk::{
ChannelHandle, ConnectionPoolConfig, PressureResponse, RemoteChannelHandle, RemoteConfig,
SchemaMetadata, SchemaValidate, SdkError, SubscriptionStream,
};
use liminal_server::config::{
AuthConfig, ChannelDef, LimitsConfig, ServerConfig, ServicesConfig, WebSocketConfig,
};
use liminal_server::server::connection::{ConnectionSupervisor, WebSocketListener};
use liminal_server::server::listener::ServerListener;
use serde::{Deserialize, Serialize};
const CHANNEL: &str = "events";
const PATH: &str = "/liminal";
const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
const RECV_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
struct OrderPlaced {
id: u64,
}
impl SchemaValidate for OrderPlaced {
fn schema_metadata() -> SchemaMetadata {
SchemaMetadata::new("orders.placed", "1", br#"{"type":"object"}"#.as_slice())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TransportKind {
Tcp,
Ws,
}
struct RunningServer {
_tcp: ServerListener,
_ws: WebSocketListener,
tcp_addr: SocketAddr,
ws_addr: SocketAddr,
}
impl RunningServer {
fn start(auth_token: Option<&str>) -> Result<Self, Box<dyn Error>> {
let health = std::net::TcpListener::bind("127.0.0.1:0")?;
let health_listen_address = health.local_addr()?;
drop(health);
let config = ServerConfig {
listen_address: "127.0.0.1:0".parse()?,
health_listen_address,
drain_timeout_ms: 30_000,
channels: vec![ChannelDef {
name: CHANNEL.to_owned(),
schema_ref: None,
durable: false,
loaded_schema: None,
}],
routing_rules: Vec::new(),
persistence_path: None,
cluster: None,
auth: auth_token.map(|token| AuthConfig {
token: token.to_owned(),
}),
services: ServicesConfig::default(),
limits: LimitsConfig::default(),
websocket: None,
participant: None,
};
let supervisor = ConnectionSupervisor::from_config(&config)?;
let tcp = ServerListener::bind(&config, supervisor.clone())?;
let ws_config = WebSocketConfig {
listen_address: "127.0.0.1:0".parse()?,
path: PATH.to_owned(),
allowed_origins: Vec::new(),
ping_interval_ms: None,
};
let ws = WebSocketListener::bind(&ws_config, supervisor)?;
let tcp_addr = tcp.local_addr();
let ws_addr = ws.local_addr();
Ok(Self {
_tcp: tcp,
_ws: ws,
tcp_addr,
ws_addr,
})
}
fn address(&self, kind: TransportKind) -> String {
match kind {
TransportKind::Tcp => self.tcp_addr.to_string(),
TransportKind::Ws => format!("ws://{}{PATH}", self.ws_addr),
}
}
}
fn connect(
server: &RunningServer,
kind: TransportKind,
auth_token: &[u8],
) -> Result<RemoteConfig, Box<dyn Error>> {
let deadline = Instant::now() + CONNECT_TIMEOUT;
let mut last_error = None;
while Instant::now() < deadline {
let config = RemoteConfig::new(
server.address(kind),
CHANNEL,
"e2e.conversation".to_owned(),
ConnectionPoolConfig::new(1, 10, 16),
)?;
let attempt = match kind {
TransportKind::Tcp => config.connect_tcp_with_auth(auth_token),
TransportKind::Ws => config.connect_websocket_with_auth(auth_token),
};
match attempt {
Ok(connected) => return Ok(connected),
Err(error) => {
last_error = Some(error);
std::thread::sleep(Duration::from_millis(20));
}
}
}
Err(last_error.map_or_else(
|| "client never connected within timeout".into(),
|error| format!("client never connected within timeout: {error}").into(),
))
}
#[test]
fn sdk_publish_parity_over_real_acceptor() -> Result<(), Box<dyn Error>> {
let server = RunningServer::start(None)?;
for kind in [TransportKind::Tcp, TransportKind::Ws] {
let config = connect(&server, kind, &[])?;
let handle = RemoteChannelHandle::new(&config)?;
let response = handle.publish(OrderPlaced { id: 7 })?;
assert_eq!(
response,
PressureResponse::Accept,
"{kind:?} publish must be accepted by the real server"
);
}
Ok(())
}
#[test]
fn sdk_subscription_delivery_parity_over_real_acceptor() -> Result<(), Box<dyn Error>> {
let server = RunningServer::start(None)?;
let tcp_stream =
SubscriptionStream::open(&server.address(TransportKind::Tcp), CHANNEL, Vec::new())?;
let ws_stream =
WebSocketSubscriptionStream::open(&server.address(TransportKind::Ws), CHANNEL, Vec::new())?;
let config = connect(&server, TransportKind::Tcp, &[])?;
let handle = RemoteChannelHandle::new(&config)?;
let ack = handle.publish_with_idempotency_key(&OrderPlaced { id: 9 }, "parity-9")?;
assert!(ack.is_accepted(), "delivery ack must be genuine");
let tcp_message = tcp_stream.recv_timeout(RECV_TIMEOUT)?;
let ws_message = ws_stream.recv_timeout(RECV_TIMEOUT)?;
assert_eq!(tcp_message.payload(), ws_message.payload());
assert_eq!(tcp_message.delivery_seq(), ws_message.delivery_seq());
Ok(())
}
#[test]
fn sdk_auth_parity_over_real_acceptor() -> Result<(), Box<dyn Error>> {
let server = RunningServer::start(Some("right-token"))?;
for kind in [TransportKind::Tcp, TransportKind::Ws] {
let refused = connect(&server, kind, b"wrong-token");
assert!(
refused.is_err(),
"{kind:?} wrong token must be refused by the real server"
);
let config = connect(&server, kind, b"right-token")?;
let handle = RemoteChannelHandle::new(&config)?;
let response = handle.publish(OrderPlaced { id: 11 })?;
assert_eq!(response, PressureResponse::Accept);
}
Ok(())
}
#[test]
fn sdk_ws_wrong_path_is_typed_refusal() -> Result<(), Box<dyn Error>> {
let server = RunningServer::start(None)?;
let config = RemoteConfig::new(
format!("ws://{}/not-liminal", server.ws_addr),
CHANNEL,
"e2e.conversation".to_owned(),
ConnectionPoolConfig::new(1, 10, 16),
)?;
let result = config.connect_websocket();
assert!(
matches!(result, Err(SdkError::Connection { .. })),
"an unconfigured path must surface as a typed connection error"
);
Ok(())
}