test: add unit tests for RPC framing, SDK state machine, and server domain services

Add comprehensive tests across three layers:
- RPC framing: empty payloads, max boundary, truncated frames, multi-frame buffers,
  all status codes, all method ID ranges, payload-too-large for response/push
- SDK: event broadcast send/receive, multiple subscribers, clone preservation,
  conversation upsert, missing conversation, message ID roundtrip, member keys
- Server domain: auth session validation/expiry, channel creation/symmetry/validation,
  delivery peek/ack/sequence ordering/fetch-limited, key package upload/fetch/validation,
  hybrid key batch fetch, size boundary tests
- CI: MSRV (1.75) check job, macOS cross-platform build check
This commit is contained in:
2026-03-08 18:07:43 +01:00
parent e4c5868b31
commit 872695e5f1
8 changed files with 1114 additions and 0 deletions

View File

@@ -85,3 +85,145 @@ pub enum ClientEvent {
/// An error occurred in the background.
Error { message: String },
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::sync::broadcast;
#[test]
fn event_broadcast_send_receive() {
let (tx, mut rx) = broadcast::channel::<ClientEvent>(16);
tx.send(ClientEvent::Connected).unwrap();
tx.send(ClientEvent::Disconnected {
reason: "test".into(),
})
.unwrap();
let e1 = rx.try_recv().unwrap();
assert!(matches!(e1, ClientEvent::Connected));
let e2 = rx.try_recv().unwrap();
assert!(matches!(e2, ClientEvent::Disconnected { reason } if reason == "test"));
}
#[test]
fn event_broadcast_multiple_subscribers() {
let (tx, mut rx1) = broadcast::channel::<ClientEvent>(16);
let mut rx2 = tx.subscribe();
tx.send(ClientEvent::Registered {
username: "alice".into(),
})
.unwrap();
let e1 = rx1.try_recv().unwrap();
let e2 = rx2.try_recv().unwrap();
assert!(matches!(e1, ClientEvent::Registered { username } if username == "alice"));
assert!(matches!(e2, ClientEvent::Registered { username } if username == "alice"));
}
#[test]
fn event_no_subscribers_does_not_panic() {
let (tx, _) = broadcast::channel::<ClientEvent>(16);
// Send with no active receiver — should return Err but not panic.
let result = tx.send(ClientEvent::Connected);
assert!(result.is_err()); // no receivers
}
#[test]
fn event_clone_preserves_data() {
let event = ClientEvent::MessageReceived {
conversation_id: [1u8; 16],
sender_key: vec![2u8; 32],
sender_name: Some("bob".into()),
body: "hello world".into(),
timestamp_ms: 12345,
};
let cloned = event.clone();
match cloned {
ClientEvent::MessageReceived {
conversation_id,
sender_key,
sender_name,
body,
timestamp_ms,
} => {
assert_eq!(conversation_id, [1u8; 16]);
assert_eq!(sender_key, vec![2u8; 32]);
assert_eq!(sender_name, Some("bob".to_string()));
assert_eq!(body, "hello world");
assert_eq!(timestamp_ms, 12345);
}
_ => panic!("wrong variant after clone"),
}
}
#[test]
fn event_debug_format() {
let event = ClientEvent::Error {
message: "something went wrong".into(),
};
let dbg = format!("{event:?}");
assert!(dbg.contains("something went wrong"));
}
#[test]
fn all_event_variants_are_clone() {
// Verify all variants can be cloned without issue.
let events: Vec<ClientEvent> = vec![
ClientEvent::Connected,
ClientEvent::Disconnected { reason: "r".into() },
ClientEvent::Reconnecting { attempt: 1 },
ClientEvent::Registered { username: "u".into() },
ClientEvent::LoggedIn { username: "u".into() },
ClientEvent::LoggedOut { username: "u".into() },
ClientEvent::Authenticated { username: "u".into() },
ClientEvent::MessageReceived {
conversation_id: [0; 16],
sender_key: vec![],
sender_name: None,
body: "b".into(),
timestamp_ms: 0,
},
ClientEvent::MessageSent {
conversation_id: [0; 16],
seq: 0,
},
ClientEvent::ConversationCreated {
conversation_id: [0; 16],
display_name: "d".into(),
},
ClientEvent::MemberAdded {
conversation_id: [0; 16],
member_key: vec![],
},
ClientEvent::MemberRemoved {
conversation_id: [0; 16],
member_key: vec![],
},
ClientEvent::PushEvent {
event_type: 0,
payload: vec![],
},
ClientEvent::MessageQueued {
outbox_id: 0,
conversation_id: [0; 16],
},
ClientEvent::OutboxFlushed { sent: 0, failed: 0 },
ClientEvent::MessageGap {
conversation_id: [0; 16],
expected_seq: 0,
received_seq: 1,
},
ClientEvent::Error { message: "e".into() },
];
for event in &events {
let _ = event.clone();
}
assert_eq!(events.len(), 17);
}
}