use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::wire::config::{Config, ErrorConvention, Handshake, HelloStyle, PushPolicy, TlsPolicy};
use crate::wire::{encode_frame, read_response, Request, Response, Value, PUSH_ID};
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use crate::server::{
spawn_listener, AuthError, Credentials, Dispatch, ListenerConfig, ListenerHandle, Principal,
PushSender, ServerInfo, Session, NOAUTH, WRONGPASS,
};
#[derive(Default)]
struct EchoDispatch {
push: Mutex<Option<PushSender>>,
}
impl Dispatch for EchoDispatch {
type Identity = ();
async fn dispatch(
&self,
session: &Session,
command: &str,
args: Vec<Value>,
) -> Result<Value, String> {
match command {
"PING" => Ok(Value::Str("PONG".to_owned())),
"ECHO" => Ok(args.into_iter().next().unwrap_or(Value::Null)),
"SLEEP" => {
let ms = args.first().and_then(Value::as_int).unwrap_or(0);
tokio::time::sleep(Duration::from_millis(ms as u64)).await;
Ok(Value::Int(ms))
}
"SUBSCRIBE" => {
let sender = session
.push_sender()
.cloned()
.ok_or_else(|| "ERR push is not enabled on this profile".to_owned())?;
*self.push.lock().unwrap() = Some(sender);
Ok(Value::Str("OK".to_owned()))
}
"WHOAMI" => Ok(session
.principal()
.map_or(Value::Null, |principal| Value::Str(principal.name))),
other => Err(format!("ERR unknown command '{other}'")),
}
}
async fn authenticate(&self, creds: Credentials) -> Result<Principal, AuthError> {
match creds {
Credentials::ApiKey(key) if key == "key-1" => Ok(Principal::new("api-key".to_owned())),
Credentials::UserPass(user, pass) if user == "root" && pass == "hunter2" => {
Ok(Principal::new(user))
}
Credentials::Token(token) if token == "tok-1" => {
Ok(Principal::new("token-user".to_owned()))
}
_ => Err(AuthError::InvalidCredentials),
}
}
fn capabilities(&self, _principal: &Principal) -> Vec<String> {
vec!["search".to_owned(), "insert".to_owned()]
}
}
fn info() -> ServerInfo {
ServerInfo {
name: "thunder-test".to_owned(),
version: "0.0.0".to_owned(),
}
}
fn config() -> ListenerConfig {
ListenerConfig::default()
}
async fn start(profile: Config) -> (ListenerHandle, Arc<EchoDispatch>) {
start_with(profile, config()).await
}
async fn start_open(profile: Config) -> (ListenerHandle, Arc<EchoDispatch>) {
start_with(profile, config().open()).await
}
async fn start_with(
profile: Config,
config: ListenerConfig,
) -> (ListenerHandle, Arc<EchoDispatch>) {
let dispatch = Arc::new(EchoDispatch::default());
let handle = spawn_listener(Arc::clone(&dispatch), profile, info(), config)
.await
.unwrap();
(handle, dispatch)
}
async fn connect(handle: &ListenerHandle) -> TcpStream {
TcpStream::connect(handle.local_addr()).await.unwrap()
}
async fn send(stream: &mut TcpStream, id: u32, command: &str, args: Vec<Value>) -> usize {
let frame = encode_frame(&Request {
id,
command: command.to_owned(),
args,
})
.unwrap();
stream.write_all(&frame).await.unwrap();
frame.len()
}
async fn recv(stream: &mut TcpStream) -> Response {
let (response, _) = read_response(stream).await.unwrap();
response
}
async fn call(stream: &mut TcpStream, id: u32, command: &str, args: Vec<Value>) -> Response {
send(stream, id, command, args).await;
recv(stream).await
}
fn auth_command_config() -> Config {
Config::standard()
.scheme("authcmd")
.port(0)
.handshake(Handshake::AuthCommand)
.hello_style(HelloStyle::NotUsed)
.push(PushPolicy::Enabled)
.error_codes(ErrorConvention::Resp3Prefixes)
}
fn argless_hello_config() -> Config {
Config::standard()
.scheme("argless")
.port(0)
.handshake(Handshake::AuthCommand)
.hello_style(HelloStyle::ArgLess)
.error_codes(ErrorConvention::Resp3Prefixes)
}
fn hello_mandatory_config() -> Config {
Config::standard()
.scheme("hellomap")
.port(0)
.error_codes(ErrorConvention::BracketCode)
}
fn tiny_profile() -> Config {
Config {
scheme: "tiny",
default_port: 0,
handshake: Handshake::None,
hello_style: HelloStyle::NotUsed,
push: PushPolicy::Reserved,
max_frame_bytes: 64,
max_in_flight: 4,
error_codes: ErrorConvention::Resp3Prefixes,
tls: TlsPolicy::Off,
}
}
fn no_handshake_profile() -> Config {
Config {
scheme: "open",
default_port: 0,
handshake: Handshake::None,
hello_style: HelloStyle::NotUsed,
push: PushPolicy::Reserved,
max_frame_bytes: crate::wire::DEFAULT_MAX_FRAME_BYTES,
max_in_flight: 16,
error_codes: ErrorConvention::Resp3Prefixes,
tls: TlsPolicy::Off,
}
}
#[tokio::test]
async fn ping_round_trips_pre_auth() {
let (handle, _dispatch) = start(argless_hello_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "PING", vec![]).await;
assert_eq!(response.id, 1);
assert_eq!(response.result, Ok(Value::Str("PONG".to_owned())));
let response = call(&mut client, 2, "PING", vec![Value::Str("hi".into())]).await;
assert_eq!(response.result, Ok(Value::Str("hi".to_owned())));
}
#[tokio::test]
async fn five_way_multiplexing_completes_out_of_order() {
let (handle, _dispatch) = start_open(auth_command_config()).await;
let mut client = connect(&handle).await;
for (id, ms) in [(1u32, 400i64), (2, 300), (3, 200), (4, 100), (5, 0)] {
send(&mut client, id, "SLEEP", vec![Value::Int(ms)]).await;
}
let mut order = Vec::new();
for _ in 0..5 {
order.push(recv(&mut client).await.id);
}
let mut sorted = order.clone();
sorted.sort_unstable();
assert_eq!(sorted, vec![1, 2, 3, 4, 5], "every request answered once");
assert_ne!(
order,
vec![1, 2, 3, 4, 5],
"completion must not follow request order"
);
assert_eq!(order.first(), Some(&5), "shortest sleep completes first");
assert_eq!(order.last(), Some(&1), "longest sleep completes last");
}
#[tokio::test]
async fn push_id_client_frame_is_refused_and_connection_stays_usable() {
let (handle, _dispatch) = start_open(auth_command_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, PUSH_ID, "ECHO", vec![Value::Int(1)]).await;
assert_eq!(response.id, PUSH_ID);
let err = response.result.unwrap_err();
assert!(
err.contains("reserved for server push"),
"dedicated refusal expected, got: {err}"
);
let response = call(&mut client, 7, "ECHO", vec![Value::Int(2)]).await;
assert_eq!(response.result, Ok(Value::Int(2)));
}
#[tokio::test]
async fn unknown_command_error_leaves_connection_usable() {
let (handle, _dispatch) = start_open(auth_command_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "NOPE", vec![]).await;
assert_eq!(
response.result,
Err("ERR unknown command 'NOPE'".to_owned())
);
let response = call(
&mut client,
2,
"ECHO",
vec![Value::Str("still alive".into())],
)
.await;
assert_eq!(response.result, Ok(Value::Str("still alive".to_owned())));
}
#[tokio::test]
async fn auth_command_profile_gates_until_auth_succeeds() {
let (handle, _dispatch) = start(argless_hello_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "ECHO", vec![Value::Int(1)]).await;
assert_eq!(response.result, Err(NOAUTH.to_owned()));
let response = call(
&mut client,
2,
"AUTH",
vec![Value::Str("root".into()), Value::Str("wrong".into())],
)
.await;
assert_eq!(response.result, Err(WRONGPASS.to_owned()));
let response = call(&mut client, 3, "ECHO", vec![Value::Int(1)]).await;
assert_eq!(response.result, Err(NOAUTH.to_owned()));
let response = call(
&mut client,
4,
"AUTH",
vec![Value::Str("root".into()), Value::Str("hunter2".into())],
)
.await;
assert_eq!(response.result, Ok(Value::Str("OK".to_owned())));
let response = call(&mut client, 5, "ECHO", vec![Value::Int(42)]).await;
assert_eq!(response.result, Ok(Value::Int(42)));
let response = call(&mut client, 6, "WHOAMI", vec![]).await;
assert_eq!(response.result, Ok(Value::Str("root".to_owned())));
}
#[tokio::test]
async fn hello_reply_matches_the_metadata_shape() {
let (handle, _dispatch) = start(argless_hello_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "HELLO", vec![Value::Int(1)]).await;
let value = response.result.unwrap();
assert_eq!(
value.map_get("server"),
Some(&Value::Str("thunder-test".to_owned()))
);
assert_eq!(
value.map_get("version"),
Some(&Value::Str("0.0.0".to_owned()))
);
assert_eq!(value.map_get("proto"), Some(&Value::Int(1)));
assert!(matches!(value.map_get("id"), Some(Value::Int(_))));
assert_eq!(value.map_get("authenticated"), Some(&Value::Bool(false)));
call(&mut client, 2, "AUTH", vec![Value::Str("key-1".into())]).await;
let response = call(&mut client, 3, "HELLO", vec![Value::Int(1)]).await;
assert_eq!(
response.result.unwrap().map_get("authenticated"),
Some(&Value::Bool(true))
);
}
#[tokio::test]
async fn hello_mandatory_rejects_non_hello_first_frame_and_closes() {
let (handle, _dispatch) = start(hello_mandatory_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "PING", vec![]).await;
assert!(
response.result.is_err(),
"non-HELLO first frame must be rejected (SRV-011)"
);
assert!(read_response(&mut client).await.is_err());
}
#[tokio::test]
async fn hello_mandatory_handshake_grants_access_and_reports_capabilities() {
let (handle, _dispatch) = start(hello_mandatory_config()).await;
let mut client = connect(&handle).await;
let hello_arg = Value::Map(vec![
(Value::Str("version".into()), Value::Int(1)),
(Value::Str("token".into()), Value::Str("tok-1".into())),
(Value::Str("client_name".into()), Value::Str("suite".into())),
]);
let response = call(&mut client, 1, "HELLO", vec![hello_arg]).await;
let value = response.result.unwrap();
assert_eq!(value.map_get("protocol_version"), Some(&Value::Int(1)));
assert_eq!(
value.map_get("capabilities"),
Some(&Value::Array(vec![
Value::Str("search".into()),
Value::Str("insert".into())
]))
);
let response = call(&mut client, 2, "ECHO", vec![Value::Int(9)]).await;
assert_eq!(response.result, Ok(Value::Int(9)));
let response = call(&mut client, 3, "WHOAMI", vec![]).await;
assert_eq!(response.result, Ok(Value::Str("token-user".to_owned())));
}
#[tokio::test]
async fn non_hello_first_frames_are_counted_for_handshake_adoption() {
let (handle, _d) = start(hello_mandatory_config()).await;
let mut client = connect(&handle).await;
let hello = Value::Map(vec![
(Value::Str("version".into()), Value::Int(1)),
(Value::Str("token".into()), Value::Str("tok-1".into())),
]);
let _ = call(&mut client, 1, "HELLO", vec![hello]).await;
assert_eq!(handle.snapshot().non_hello_first_frames_total, 0);
drop(client);
handle.stop().await;
let (handle, _d) = start(argless_hello_config()).await;
let mut c1 = connect(&handle).await;
let _ = call(&mut c1, 1, "PING", vec![]).await;
let mut c2 = connect(&handle).await;
let _ = call(&mut c2, 1, "PING", vec![]).await;
assert_eq!(
handle.snapshot().non_hello_first_frames_total,
2,
"two connections led with a non-HELLO frame"
);
drop(c1);
drop(c2);
handle.stop().await;
}
#[tokio::test]
async fn hello_mandatory_bad_credentials_error_allows_retry() {
let (handle, _dispatch) = start(hello_mandatory_config()).await;
let mut client = connect(&handle).await;
let bad = Value::Map(vec![(
Value::Str("token".into()),
Value::Str("nope".into()),
)]);
let response = call(&mut client, 1, "HELLO", vec![bad]).await;
assert_eq!(
response.result,
Err("[unauthorized] invalid credentials".to_owned())
);
let response = call(&mut client, 2, "ECHO", vec![Value::Int(1)]).await;
assert_eq!(
response.result,
Err("[unauthorized] authentication required: send HELLO first".to_owned())
);
let good = Value::Map(vec![(
Value::Str("token".into()),
Value::Str("tok-1".into()),
)]);
let response = call(&mut client, 3, "HELLO", vec![good]).await;
assert!(response.result.is_ok());
let response = call(&mut client, 4, "ECHO", vec![Value::Int(1)]).await;
assert_eq!(response.result, Ok(Value::Int(1)));
}
#[tokio::test]
async fn auth_command_shape_gates_until_auth_when_the_deployment_requires_it() {
let (handle, _dispatch) = start(auth_command_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "ECHO", vec![Value::Int(1)]).await;
assert_eq!(response.result, Err(NOAUTH.to_owned()));
let response = call(
&mut client,
2,
"AUTH",
vec![Value::Str("root".into()), Value::Str("hunter2".into())],
)
.await;
assert!(response.result.is_ok(), "AUTH must succeed: {response:?}");
let response = call(&mut client, 3, "ECHO", vec![Value::Int(1)]).await;
assert_eq!(response.result, Ok(Value::Int(1)));
let response = call(&mut client, 4, "WHOAMI", vec![]).await;
assert_eq!(response.result, Ok(Value::Str("root".to_owned())));
}
#[tokio::test]
async fn auth_command_profile_serves_uncredentialed_sessions_when_open() {
let (handle, _dispatch) = start_open(auth_command_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "ECHO", vec![Value::Int(7)]).await;
assert_eq!(
response.result,
Ok(Value::Int(7)),
"an open deployment must not demand AUTH"
);
}
#[tokio::test]
async fn open_deployment_still_rejects_bad_credentials() {
let (handle, _dispatch) = start_open(auth_command_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "AUTH", vec![Value::Str("nope".into())]).await;
assert_eq!(response.result, Err(WRONGPASS.to_owned()));
}
#[tokio::test]
async fn handshake_none_dispatches_the_first_frame() {
let (handle, _dispatch) = start(no_handshake_profile()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "ECHO", vec![Value::Str("first".into())]).await;
assert_eq!(response.result, Ok(Value::Str("first".to_owned())));
}
#[tokio::test]
async fn oversized_frame_closes_the_connection_without_killing_the_listener() {
let (handle, _dispatch) = start(tiny_profile()).await;
let mut client = connect(&handle).await;
client.write_all(&1024u32.to_le_bytes()).await.unwrap();
assert!(
read_response(&mut client).await.is_err(),
"connection must be closed"
);
let mut second = connect(&handle).await;
let response = call(&mut second, 1, "ECHO", vec![Value::Int(5)]).await;
assert_eq!(response.result, Ok(Value::Int(5)));
}
#[tokio::test]
async fn malformed_body_closes_only_that_connection() {
let (handle, _dispatch) = start_open(auth_command_config()).await;
let mut bad = connect(&handle).await;
let mut good = connect(&handle).await;
bad.write_all(&4u32.to_le_bytes()).await.unwrap();
bad.write_all(&[0xc1, 0xc1, 0xc1, 0xc1]).await.unwrap();
assert!(
read_response(&mut bad).await.is_err(),
"malformed body closes the connection"
);
let response = call(&mut good, 1, "ECHO", vec![Value::Int(3)]).await;
assert_eq!(response.result, Ok(Value::Int(3)));
}
#[tokio::test]
async fn push_frames_flow_when_push_is_enabled() {
let (handle, dispatch) = start_open(auth_command_config()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "SUBSCRIBE", vec![]).await;
assert_eq!(response.result, Ok(Value::Str("OK".to_owned())));
let sender = dispatch.push.lock().unwrap().clone().unwrap();
sender.push(Value::Str("event-1".to_owned())).await.unwrap();
let push = recv(&mut client).await;
assert_eq!(push.id, PUSH_ID);
assert_eq!(push.result, Ok(Value::Str("event-1".to_owned())));
let response = call(&mut client, 2, "ECHO", vec![Value::Int(1)]).await;
assert_eq!(response.result, Ok(Value::Int(1)));
drop(client);
handle.stop().await;
assert!(sender.push(Value::Null).await.is_err());
}
#[tokio::test]
async fn push_reserved_profile_exposes_no_push_sender() {
let (handle, _dispatch) = start(argless_hello_config()).await;
let mut client = connect(&handle).await;
call(&mut client, 1, "AUTH", vec![Value::Str("key-1".into())]).await;
let response = call(&mut client, 2, "SUBSCRIBE", vec![]).await;
assert_eq!(
response.result,
Err("ERR push is not enabled on this profile".to_owned())
);
}
#[tokio::test]
async fn metrics_snapshot_counts_after_successful_writes() {
let mut cfg = config();
cfg.slow_threshold = Duration::from_millis(5);
let (handle, _dispatch) = start_with(auth_command_config(), cfg.open()).await;
let mut client = connect(&handle).await;
let request_frame_len = send(&mut client, 1, "ECHO", vec![Value::Str("hi".into())]).await;
let response = recv(&mut client).await;
let response_frame_len = encode_frame(&response).unwrap().len();
let snap = handle.snapshot();
assert_eq!(snap.connections, 1);
assert_eq!(snap.commands_total, 1);
assert_eq!(snap.commands_error_total, 0);
assert_eq!(snap.frame_bytes_in_total, request_frame_len as u64);
assert_eq!(snap.frame_bytes_out_total, response_frame_len as u64);
call(&mut client, 2, "NOPE", vec![]).await;
call(&mut client, 3, "SLEEP", vec![Value::Int(50)]).await;
let snap = handle.snapshot();
assert_eq!(snap.commands_total, 3);
assert_eq!(snap.commands_error_total, 1);
assert_eq!(snap.slow_commands_total, 1);
assert!(snap.command_duration_microseconds_total >= 45_000);
}
#[tokio::test]
async fn idle_timeout_closes_a_silent_connection() {
let mut cfg = config();
cfg.idle_timeout = Duration::from_millis(100);
let (handle, _dispatch) = start_with(auth_command_config(), cfg.open()).await;
let mut client = connect(&handle).await;
let response = call(&mut client, 1, "ECHO", vec![Value::Int(1)]).await;
assert!(response.result.is_ok());
let read = tokio::time::timeout(Duration::from_secs(5), read_response(&mut client)).await;
assert!(
matches!(read, Ok(Err(_))),
"expected EOF after idle timeout, got {read:?}"
);
}
#[tokio::test]
async fn stop_drains_in_flight_requests_before_closing() {
let (handle, _dispatch) = start_open(auth_command_config()).await;
let mut client = connect(&handle).await;
send(&mut client, 1, "SLEEP", vec![Value::Int(150)]).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let addr = handle.local_addr();
handle.stop().await;
let response = recv(&mut client).await;
assert_eq!(response.id, 1);
assert_eq!(response.result, Ok(Value::Int(150)));
assert!(
read_response(&mut client).await.is_err(),
"connection closed after drain"
);
assert!(
TcpStream::connect(addr).await.is_err(),
"listener no longer accepts"
);
}
#[test]
fn error_helpers_format_the_family_conventions() {
assert_eq!(
crate::server::format_bracket_code("not_found", "no such collection"),
"[not_found] no such collection"
);
assert_eq!(crate::server::format_err("boom"), "ERR boom");
assert!(NOAUTH.starts_with("NOAUTH "));
assert!(WRONGPASS.starts_with("WRONGPASS "));
}
#[tokio::test]
async fn max_connections_refuses_past_the_ceiling_and_counts_it() {
let (handle, _d) = start_with(no_handshake_profile(), config().with_max_connections(2)).await;
let _a = connect(&handle).await;
let _b = connect(&handle).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let mut refused = TcpStream::connect(handle.local_addr()).await.unwrap();
let mut buf = [0u8; 1];
let read = tokio::time::timeout(
Duration::from_secs(2),
tokio::io::AsyncReadExt::read(&mut refused, &mut buf),
)
.await
.expect("a refused connection must not hang");
assert_eq!(read.unwrap(), 0, "refused connection is closed, not queued");
assert_eq!(
handle.snapshot().connections_refused_total,
1,
"the ceiling must be observable, not silent"
);
}
#[tokio::test]
async fn max_connections_frees_a_slot_when_a_connection_closes() {
let (handle, _d) = start_with(no_handshake_profile(), config().with_max_connections(1)).await;
let first = connect(&handle).await;
tokio::time::sleep(Duration::from_millis(50)).await;
drop(first);
tokio::time::sleep(Duration::from_millis(150)).await;
let mut second = connect(&handle).await;
let response = call(&mut second, 1, "PING", vec![]).await;
assert_eq!(response.result.unwrap(), Value::Str("PONG".to_owned()));
}
#[tokio::test]
async fn max_connections_defaults_to_unbounded() {
assert_eq!(ListenerConfig::default().max_connections, 0);
let (handle, _d) = start(no_handshake_profile()).await;
let mut conns = Vec::new();
for _ in 0..8 {
conns.push(connect(&handle).await);
}
tokio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(handle.snapshot().connections_refused_total, 0);
}
#[tokio::test]
async fn a_shared_handle_can_both_observe_and_stop() {
let (handle, _d) = start(no_handshake_profile()).await;
let handle = Arc::new(handle);
let reader = handle.metrics();
let observer = Arc::clone(&handle);
assert_eq!(reader.snapshot().connections, 0);
assert_eq!(observer.snapshot().connections, 0);
let mut conn = connect(&handle).await;
let _ = call(&mut conn, 1, "PING", vec![]).await;
assert_eq!(reader.snapshot().commands_total, 1);
handle.stop().await;
assert!(TcpStream::connect(handle.local_addr()).await.is_err());
}
#[tokio::test]
async fn stopping_twice_is_safe() {
let (handle, _d) = start(no_handshake_profile()).await;
handle.stop().await;
tokio::time::timeout(Duration::from_secs(2), handle.stop())
.await
.expect("a second stop must return, not hang");
}
#[tokio::test]
async fn the_metrics_observer_sees_each_command_with_its_label() {
#[derive(Default)]
struct Recorder {
seen: Mutex<Vec<(String, usize, usize, bool)>>,
opened: AtomicUsize,
closed: AtomicUsize,
}
impl crate::server::MetricsObserver for Recorder {
fn command_completed(
&self,
command: &str,
in_bytes: usize,
out_bytes: usize,
_duration: Duration,
is_error: bool,
) {
self.seen
.lock()
.unwrap()
.push((command.to_owned(), in_bytes, out_bytes, is_error));
}
fn connection_opened(&self) {
self.opened.fetch_add(1, AtomicOrdering::Relaxed);
}
fn connection_closed(&self) {
self.closed.fetch_add(1, AtomicOrdering::Relaxed);
}
}
let recorder = Arc::new(Recorder::default());
let observer: Arc<dyn crate::server::MetricsObserver> = Arc::clone(&recorder) as _;
let (handle, _d) = start_with(no_handshake_profile(), config().with_observer(observer)).await;
let mut conn = connect(&handle).await;
let ping_in = send(&mut conn, 1, "PING", vec![]).await;
let _ = recv(&mut conn).await;
let bad_in = send(&mut conn, 2, "NOPE", vec![]).await;
let _ = recv(&mut conn).await;
drop(conn);
tokio::time::sleep(Duration::from_millis(150)).await;
let seen = recorder.seen.lock().unwrap().clone();
assert_eq!(seen.len(), 2, "one callback per completed command");
let (command, in_bytes, out_bytes, is_error) = &seen[0];
assert_eq!(command, "PING", "the label is what totals cannot give");
assert_eq!(
*in_bytes, ping_in,
"in-bytes match the decoder's frame size"
);
assert!(*out_bytes > 0);
assert!(!*is_error);
let (command, in_bytes, _, is_error) = &seen[1];
assert_eq!(command, "NOPE");
assert_eq!(*in_bytes, bad_in);
assert!(*is_error, "an Err response is flagged as an error");
assert_eq!(recorder.opened.load(AtomicOrdering::Relaxed), 1);
assert_eq!(recorder.closed.load(AtomicOrdering::Relaxed), 1);
}
#[tokio::test]
async fn observer_and_builtin_metrics_agree() {
#[derive(Default)]
struct Sum {
in_bytes: AtomicUsize,
out_bytes: AtomicUsize,
}
impl crate::server::MetricsObserver for Sum {
fn command_completed(
&self,
_command: &str,
in_bytes: usize,
out_bytes: usize,
_duration: Duration,
_is_error: bool,
) {
self.in_bytes.fetch_add(in_bytes, AtomicOrdering::Relaxed);
self.out_bytes.fetch_add(out_bytes, AtomicOrdering::Relaxed);
}
}
let sum = Arc::new(Sum::default());
let observer: Arc<dyn crate::server::MetricsObserver> = Arc::clone(&sum) as _;
let (handle, _d) = start_with(no_handshake_profile(), config().with_observer(observer)).await;
let mut conn = connect(&handle).await;
for id in 1..=5 {
let _ = call(&mut conn, id, "PING", vec![]).await;
}
tokio::time::sleep(Duration::from_millis(150)).await;
let snapshot = handle.snapshot();
assert_eq!(
sum.in_bytes.load(AtomicOrdering::Relaxed) as u64,
snapshot.frame_bytes_in_total
);
assert_eq!(
sum.out_bytes.load(AtomicOrdering::Relaxed) as u64,
snapshot.frame_bytes_out_total
);
}
#[tokio::test]
async fn no_observer_means_no_observer_work() {
let (handle, _d) = start(no_handshake_profile()).await;
assert!(ListenerConfig::default().observer.is_none());
let mut conn = connect(&handle).await;
let _ = call(&mut conn, 1, "PING", vec![]).await;
assert_eq!(handle.snapshot().commands_total, 1);
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct User {
is_admin: bool,
tenant: String,
}
#[derive(Default)]
struct AclDispatch {
lookups: AtomicUsize,
}
impl Dispatch for AclDispatch {
type Identity = User;
async fn dispatch(
&self,
session: &Session<User>,
command: &str,
_args: Vec<Value>,
) -> Result<Value, String> {
match command {
"ADMIN_ONLY" => {
let is_admin = session.with_principal(|p| p.is_some_and(|p| p.identity.is_admin));
if is_admin {
Ok(Value::Str("granted".to_owned()))
} else {
Err(crate::server::NOPERM.to_owned())
}
}
"WHOAMI" => Ok(Value::Str(
session
.principal_name()
.unwrap_or_else(|| "anon".to_owned()),
)),
"TENANT" => Ok(Value::Str(session.with_principal(|p| {
p.map(|p| p.identity.tenant.clone()).unwrap_or_default()
}))),
_ => Ok(Value::Null),
}
}
async fn authenticate(&self, creds: Credentials) -> Result<Principal<User>, AuthError> {
self.lookups.fetch_add(1, AtomicOrdering::Relaxed);
match creds {
Credentials::UserPass(user, pass) if pass == "pw" => Ok(Principal::with_identity(
user.clone(),
User {
is_admin: user == "root",
tenant: "acme".to_owned(),
},
)),
_ => Err(AuthError::InvalidCredentials),
}
}
}
async fn start_acl() -> (ListenerHandle, Arc<AclDispatch>) {
let dispatch = Arc::new(AclDispatch::default());
let handle = spawn_listener(
Arc::clone(&dispatch),
auth_command_config(),
info(),
config(),
)
.await
.unwrap();
(handle, dispatch)
}
#[tokio::test]
async fn the_product_identity_survives_to_authorization_without_a_second_lookup() {
let (handle, dispatch) = start_acl().await;
let mut conn = connect(&handle).await;
let auth = call(
&mut conn,
1,
"AUTH",
vec![Value::Str("root".to_owned()), Value::Str("pw".to_owned())],
)
.await;
assert!(auth.result.is_ok(), "AUTH should succeed: {auth:?}");
assert_eq!(dispatch.lookups.load(AtomicOrdering::Relaxed), 1);
for id in 2..=11 {
let response = call(&mut conn, id, "ADMIN_ONLY", vec![]).await;
assert_eq!(response.result.unwrap(), Value::Str("granted".to_owned()));
}
assert_eq!(
dispatch.lookups.load(AtomicOrdering::Relaxed),
1,
"authorization must read the session, not re-resolve the user per command"
);
}
#[tokio::test]
async fn identity_is_captured_at_auth_not_re_read_per_command() {
let (handle, _d) = start_acl().await;
let mut conn = connect(&handle).await;
let _ = call(
&mut conn,
1,
"AUTH",
vec![Value::Str("root".to_owned()), Value::Str("pw".to_owned())],
)
.await;
let tenant = call(&mut conn, 2, "TENANT", vec![]).await;
assert_eq!(tenant.result.unwrap(), Value::Str("acme".to_owned()));
let who = call(&mut conn, 3, "WHOAMI", vec![]).await;
assert_eq!(who.result.unwrap(), Value::Str("root".to_owned()));
}
#[tokio::test]
async fn a_non_admin_identity_is_refused() {
let (handle, _d) = start_acl().await;
let mut conn = connect(&handle).await;
let _ = call(
&mut conn,
1,
"AUTH",
vec![Value::Str("alice".to_owned()), Value::Str("pw".to_owned())],
)
.await;
let denied = call(&mut conn, 2, "ADMIN_ONLY", vec![]).await;
assert!(denied.result.is_err(), "a non-admin must be refused");
}
#[test]
fn the_identity_defaults_to_unit_for_products_that_do_not_need_one() {
let principal = Principal::new("someone");
assert_eq!(principal.name, "someone");
assert_eq!(principal.identity, ());
}