feat(auth): add authentication protocol definitions and build configuration

- Add TokenClaims message for JWT payload structure with user id, issuer, timestamps, and scopes
- Implement IssueTokenRequest/Response for creating access and refresh tokens with TTL support
- Create RefreshTokenRequest/Response for token rotation functionality
- Define RevokeTokenRequest/Response with support for single token or user-wide revocation
- Add VerifyTokenRequest/Response for validating JWT tokens with detailed claims information
- Implement signing key distribution system with GetSigningKeysRequest/Response
- Create TokenService gRPC service with IssueToken, RefreshToken, RevokeToken, VerifyToken, and GetSigningKeys methods
- Add build.rs configuration to compile proto files using tonic_prost_build
- Include channel, channel_settings, member, and permission protocol definitions for IM services
- Generate Rust code bindings through pb/core.rs and pb/im.rs modules
This commit is contained in:
zhenyi
2026-06-10 23:45:40 +08:00
commit 06e8ee96a5
43 changed files with 9671 additions and 0 deletions
+344
View File
@@ -0,0 +1,344 @@
use std::collections::HashSet;
use std::sync::Arc;
use imks::socket::adapter::{Adapter, AdapterError, BroadcastOptions, BroadcastFlags, BusMessage, LocalAdapter, SocketInfo};
use imks::socket::packet::Packet;
use imks::socket::session_store::{InMemorySessionStore, SessionInfo, SessionStoreTrait};
#[tokio::test]
async fn test_local_adapter_add_and_del() {
let sent_packets: dashmap::DashMap<String, Vec<Packet>> = dashmap::DashMap::new();
let sent_packets_clone = sent_packets.clone();
let send_fn = move |engine_sid: &str, packet: &Packet| {
sent_packets_clone.entry(engine_sid.to_string()).or_insert_with(Vec::new).value_mut().push(packet.clone());
Ok(())
};
let adapter = LocalAdapter::new(send_fn);
adapter.add("sid1", "room1", "/").await.unwrap();
adapter.add("sid1", "room2", "/").await.unwrap();
adapter.add("sid2", "room1", "/").await.unwrap();
let rooms = adapter.socket_rooms("sid1").await.unwrap();
assert!(rooms.contains("room1"));
assert!(rooms.contains("room2"));
adapter.del("sid1", "room1", "/").await.unwrap();
let rooms = adapter.socket_rooms("sid1").await.unwrap();
assert!(!rooms.contains("room1"));
assert!(rooms.contains("room2"));
}
#[tokio::test]
async fn test_local_adapter_del_all() {
let send_fn = move |_engine_sid: &str, _packet: &Packet| Ok(());
let adapter = LocalAdapter::new(send_fn);
adapter.add("sid1", "room1", "/").await.unwrap();
adapter.add("sid1", "room2", "/").await.unwrap();
adapter.del_all("sid1", "/").await.unwrap();
let rooms = adapter.socket_rooms("sid1").await.unwrap();
assert!(rooms.is_empty());
}
#[tokio::test]
async fn test_local_adapter_register_and_broadcast() {
let sent_packets: Arc<dashmap::DashMap<String, Vec<Packet>>> = Arc::new(dashmap::DashMap::new());
let sent_packets_clone = sent_packets.clone();
let send_fn = move |engine_sid: &str, packet: &Packet| {
sent_packets_clone.entry(engine_sid.to_string()).or_insert_with(Vec::new).value_mut().push(packet.clone());
Ok(())
};
let adapter = LocalAdapter::new(send_fn);
// Register socket_sid → engine_sid mapping
adapter.register("sid1", "engine1", "/").await.unwrap();
adapter.register("sid2", "engine2", "/").await.unwrap();
let packet = Packet::event("/", serde_json::json!(["test", "hello"]), None);
let opts = BroadcastOptions::default();
adapter.broadcast(&packet, &opts).await.unwrap();
assert!(sent_packets.contains_key("engine1"));
assert!(sent_packets.contains_key("engine2"));
assert_eq!(sent_packets.len(), 2);
}
#[tokio::test]
async fn test_local_adapter_broadcast_to_room() {
let sent_packets: Arc<dashmap::DashMap<String, Vec<Packet>>> = Arc::new(dashmap::DashMap::new());
let sent_packets_clone = sent_packets.clone();
let send_fn = move |engine_sid: &str, packet: &Packet| {
sent_packets_clone.entry(engine_sid.to_string()).or_insert_with(Vec::new).value_mut().push(packet.clone());
Ok(())
};
let adapter = LocalAdapter::new(send_fn);
adapter.register("sid1", "engine1", "/").await.unwrap();
adapter.register("sid2", "engine2", "/").await.unwrap();
adapter.add("sid1", "room1", "/").await.unwrap();
adapter.add("sid2", "room2", "/").await.unwrap();
let packet = Packet::event("/", serde_json::json!(["test", "hello"]), None);
let opts = BroadcastOptions {
rooms: HashSet::from(["room1".to_string()]),
except: HashSet::new(),
flags: BroadcastFlags::default(),
};
adapter.broadcast(&packet, &opts).await.unwrap();
assert!(sent_packets.contains_key("engine1"));
assert!(!sent_packets.contains_key("engine2"));
}
#[tokio::test]
async fn test_local_adapter_broadcast_except() {
let sent_packets: Arc<dashmap::DashMap<String, Vec<Packet>>> = Arc::new(dashmap::DashMap::new());
let sent_packets_clone = sent_packets.clone();
let send_fn = move |engine_sid: &str, packet: &Packet| {
sent_packets_clone.entry(engine_sid.to_string()).or_insert_with(Vec::new).value_mut().push(packet.clone());
Ok(())
};
let adapter = LocalAdapter::new(send_fn);
adapter.register("sid1", "engine1", "/").await.unwrap();
adapter.register("sid2", "engine2", "/").await.unwrap();
let packet = Packet::event("/", serde_json::json!(["test", "hello"]), None);
let opts = BroadcastOptions {
rooms: HashSet::new(),
except: HashSet::from(["sid1".to_string()]),
flags: BroadcastFlags::default(),
};
adapter.broadcast(&packet, &opts).await.unwrap();
assert!(!sent_packets.contains_key("engine1"));
assert!(sent_packets.contains_key("engine2"));
}
#[tokio::test]
async fn test_local_adapter_fetch_sockets() {
let send_fn = move |_engine_sid: &str, _packet: &Packet| Ok(());
let adapter = LocalAdapter::new(send_fn);
adapter.register("sid1", "engine1", "/").await.unwrap();
adapter.register("sid2", "engine2", "/").await.unwrap();
adapter.add("sid1", "room1", "/").await.unwrap();
adapter.add("sid2", "room2", "/").await.unwrap();
let opts = BroadcastOptions::default();
let sockets = adapter.fetch_sockets(&opts).await.unwrap();
assert_eq!(sockets.len(), 2);
}
#[tokio::test]
async fn test_local_adapter_server_id_unique() {
let send_fn1 = move |_engine_sid: &str, _packet: &Packet| Ok(());
let send_fn2 = move |_engine_sid: &str, _packet: &Packet| Ok(());
let adapter1 = LocalAdapter::new(send_fn1);
let adapter2 = LocalAdapter::new(send_fn2);
assert_ne!(adapter1.server_id(), adapter2.server_id());
}
#[tokio::test]
async fn test_in_memory_session_store() {
let store = InMemorySessionStore::new();
store.create("sid1", "polling", "server1").await.unwrap();
assert!(store.exists("sid1").await.unwrap());
let info = store.get("sid1").await.unwrap().unwrap();
assert_eq!(info.sid, "sid1");
assert_eq!(info.transport, "polling");
assert_eq!(info.state, "connecting");
assert_eq!(info.server_id, "server1");
store.set_state("sid1", "open").await.unwrap();
let info = store.get("sid1").await.unwrap().unwrap();
assert_eq!(info.state, "open");
store.set_transport("sid1", "websocket").await.unwrap();
let info = store.get("sid1").await.unwrap().unwrap();
assert_eq!(info.transport, "websocket");
store.update_ping("sid1").await.unwrap();
let info = store.get("sid1").await.unwrap().unwrap();
assert!(info.last_ping > 0);
store.remove("sid1").await.unwrap();
assert!(!store.exists("sid1").await.unwrap());
}
#[tokio::test]
async fn test_in_memory_session_store_not_found() {
let store = InMemorySessionStore::new();
let result = store.get("nonexistent").await.unwrap();
assert!(result.is_none());
let result = store.set_state("nonexistent", "open").await;
assert!(result.is_err());
}
#[test]
fn test_bus_message_serialization() {
let msg = BusMessage::Broadcast {
namespace: "/".to_string(),
packet: "2[\"hello\"]".to_string(),
opts: BroadcastOptions::default(),
server_id: "server-1".to_string(),
};
let encoded = serde_json::to_vec(&msg).unwrap();
let decoded: BusMessage = serde_json::from_slice(&encoded).unwrap();
assert_eq!(decoded, msg);
}
#[test]
fn test_bus_message_socket_join() {
let msg = BusMessage::SocketJoin {
namespace: "/admin".to_string(),
sid: "sid-1".to_string(),
room: "room-1".to_string(),
server_id: "server-1".to_string(),
};
let encoded = serde_json::to_vec(&msg).unwrap();
let decoded: BusMessage = serde_json::from_slice(&encoded).unwrap();
assert_eq!(decoded, msg);
}
#[test]
fn test_bus_message_socket_leave() {
let msg = BusMessage::SocketLeave {
namespace: "/".to_string(),
sid: "sid-1".to_string(),
room: "room-1".to_string(),
server_id: "server-1".to_string(),
};
let encoded = serde_json::to_vec(&msg).unwrap();
let decoded: BusMessage = serde_json::from_slice(&encoded).unwrap();
assert_eq!(decoded, msg);
}
#[test]
fn test_bus_message_socket_disconnect() {
let msg = BusMessage::SocketDisconnect {
namespace: "/".to_string(),
sid: "sid-1".to_string(),
server_id: "server-1".to_string(),
};
let encoded = serde_json::to_vec(&msg).unwrap();
let decoded: BusMessage = serde_json::from_slice(&encoded).unwrap();
assert_eq!(decoded, msg);
}
#[test]
fn test_broadcast_options_serialization() {
let opts = BroadcastOptions {
rooms: HashSet::from(["room1".to_string(), "room2".to_string()]),
except: HashSet::from(["sid1".to_string()]),
flags: BroadcastFlags {
local_only: true,
broadcast: false,
},
};
let encoded = serde_json::to_vec(&opts).unwrap();
let decoded: BroadcastOptions = serde_json::from_slice(&encoded).unwrap();
assert_eq!(decoded.rooms, opts.rooms);
assert_eq!(decoded.except, opts.except);
assert_eq!(decoded.flags.local_only, opts.flags.local_only);
}
#[test]
fn test_socket_info_serialization() {
let info = SocketInfo {
sid: "sid-1".to_string(),
namespace: "/admin".to_string(),
rooms: HashSet::from(["room1".to_string()]),
};
let encoded = serde_json::to_vec(&info).unwrap();
let decoded: SocketInfo = serde_json::from_slice(&encoded).unwrap();
assert_eq!(decoded.sid, info.sid);
assert_eq!(decoded.namespace, info.namespace);
}
#[test]
fn test_session_info_serialization() {
let info = SessionInfo {
sid: "sid-1".to_string(),
transport: "websocket".to_string(),
state: "open".to_string(),
server_id: "server-1".to_string(),
created_at: 1234567890,
last_ping: 1234567900,
};
let encoded = serde_json::to_vec(&info).unwrap();
let decoded: SessionInfo = serde_json::from_slice(&encoded).unwrap();
assert_eq!(decoded.sid, info.sid);
assert_eq!(decoded.transport, info.transport);
assert_eq!(decoded.state, info.state);
}
#[test]
fn test_message_bus_error_display() {
let err = imks::socket::message_bus::MessageBusError::Redis("connection refused".to_string());
assert_eq!(format!("{}", err), "Redis error: connection refused");
let err = imks::socket::message_bus::MessageBusError::Nats("timeout".to_string());
assert_eq!(format!("{}", err), "NATS error: timeout");
}
#[test]
fn test_adapter_error_display() {
let err = AdapterError::Redis("SADD failed".to_string());
assert_eq!(format!("{}", err), "Redis error: SADD failed");
let err = AdapterError::Nats("publish failed".to_string());
assert_eq!(format!("{}", err), "NATS error: publish failed");
let err = AdapterError::Serialization("json error".to_string());
assert_eq!(format!("{}", err), "Serialization error: json error");
}
#[test]
fn test_session_error_display() {
let err = imks::socket::session_store::SessionError::Redis("timeout".to_string());
assert_eq!(format!("{}", err), "Redis error: timeout");
let err = imks::socket::session_store::SessionError::NotFound("sid-1".to_string());
assert_eq!(format!("{}", err), "Session not found: sid-1");
}
#[test]
fn test_is_valid_namespace() {
assert!(imks::socket::namespace::is_valid_namespace("/"));
assert!(imks::socket::namespace::is_valid_namespace("/admin"));
assert!(imks::socket::namespace::is_valid_namespace("/chat/room1"));
assert!(!imks::socket::namespace::is_valid_namespace(""));
assert!(!imks::socket::namespace::is_valid_namespace("admin"));
assert!(!imks::socket::namespace::is_valid_namespace(&"/".repeat(257)));
}
+158
View File
@@ -0,0 +1,158 @@
use imks::engine::codec;
use imks::engine::packet::{HandshakeData, Packet, PacketData, PacketType};
#[test]
fn test_engine_io_handshake_encoding() {
let handshake = HandshakeData {
sid: "lv_VI97HAXpY6yYWAAAC".to_string(),
upgrades: vec!["websocket".to_string()],
ping_interval: 25000,
ping_timeout: 20000,
max_payload: 1000000,
};
let packet = Packet::open(&handshake);
let encoded = codec::encode_packet(&packet);
assert!(encoded.starts_with('0'));
assert!(encoded.contains("\"sid\":\"lv_VI97HAXpY6yYWAAAC\""));
assert!(encoded.contains("\"upgrades\":[\"websocket\"]"));
assert!(encoded.contains("\"pingInterval\":25000"));
assert!(encoded.contains("\"pingTimeout\":20000"));
assert!(encoded.contains("\"maxPayload\":1000000"));
}
#[test]
fn test_engine_io_packet_types() {
let open = Packet::open(&HandshakeData {
sid: "test".to_string(),
upgrades: vec![],
ping_interval: 25000,
ping_timeout: 20000,
max_payload: 1000000,
});
assert_eq!(open.packet_type, PacketType::Open);
let close = Packet::close();
assert_eq!(close.packet_type, PacketType::Close);
let ping = Packet::ping("test");
assert_eq!(ping.packet_type, PacketType::Ping);
let pong = Packet::pong("test");
assert_eq!(pong.packet_type, PacketType::Pong);
let msg = Packet::message_text("hello");
assert_eq!(msg.packet_type, PacketType::Message);
let upgrade = Packet::upgrade();
assert_eq!(upgrade.packet_type, PacketType::Upgrade);
let noop = Packet::noop();
assert_eq!(noop.packet_type, PacketType::Noop);
}
#[test]
fn test_engine_io_polling_payload() {
let packets = vec![
Packet::message_text("hello"),
Packet::ping(""),
Packet::message_text("world"),
];
let encoded = codec::encode_payload(&packets);
assert_eq!(encoded, "4hello\x1e2\x1e4world");
let decoded = codec::decode_payload(&encoded).unwrap();
assert_eq!(decoded.len(), 3);
assert_eq!(decoded[0].packet_type, PacketType::Message);
assert_eq!(decoded[1].packet_type, PacketType::Ping);
assert_eq!(decoded[2].packet_type, PacketType::Message);
}
#[test]
fn test_engine_io_binary_encoding() {
let packet = Packet::message_binary(vec![0x01, 0x02, 0x03, 0x04]);
let encoded = codec::encode_packet(&packet);
assert_eq!(encoded, "4bAQIDBA==");
let decoded = codec::decode_packet(&encoded).unwrap();
assert_eq!(decoded.packet_type, PacketType::Message);
assert_eq!(decoded.data, PacketData::Binary(vec![0x01, 0x02, 0x03, 0x04]));
}
#[test]
fn test_engine_io_webtransport_header_encoding() {
let header = codec::encode_webtransport_header(6, false);
assert_eq!(header, vec![0x06]);
let header = codec::encode_webtransport_header(200, true);
assert_eq!(header.len(), 3);
assert_eq!(header[0], 0x80 | 126);
let header = codec::encode_webtransport_header(70000, false);
assert_eq!(header.len(), 9);
assert_eq!(header[0], 127);
}
#[test]
fn test_engine_io_webtransport_header_decoding() {
let header = vec![0x06];
let (len, is_binary) = codec::decode_webtransport_header(&header).unwrap();
assert_eq!(len, 6);
assert!(!is_binary);
let header = vec![0x80 | 126, 0x00, 0xC8];
let (len, is_binary) = codec::decode_webtransport_header(&header).unwrap();
assert_eq!(len, 200);
assert!(is_binary);
}
#[test]
fn test_engine_io_probe_ping_pong() {
let ping = Packet::ping("probe");
let encoded = codec::encode_packet(&ping);
assert_eq!(encoded, "2probe");
let decoded = codec::decode_packet(&encoded).unwrap();
assert_eq!(decoded.packet_type, PacketType::Ping);
assert_eq!(decoded.data, PacketData::Text("probe".to_string()));
let pong = Packet::pong("probe");
let encoded = codec::encode_packet(&pong);
assert_eq!(encoded, "3probe");
let decoded = codec::decode_packet(&encoded).unwrap();
assert_eq!(decoded.packet_type, PacketType::Pong);
assert_eq!(decoded.data, PacketData::Text("probe".to_string()));
}
#[test]
fn test_engine_io_upgrade_packet() {
let upgrade = Packet::upgrade();
let encoded = codec::encode_packet(&upgrade);
assert_eq!(encoded, "5");
let decoded = codec::decode_packet(&encoded).unwrap();
assert_eq!(decoded.packet_type, PacketType::Upgrade);
}
#[test]
fn test_engine_io_noop_packet() {
let noop = Packet::noop();
let encoded = codec::encode_packet(&noop);
assert_eq!(encoded, "6");
let decoded = codec::decode_packet(&encoded).unwrap();
assert_eq!(decoded.packet_type, PacketType::Noop);
}
#[test]
fn test_engine_io_close_packet() {
let close = Packet::close();
let encoded = codec::encode_packet(&close);
assert_eq!(encoded, "1");
let decoded = codec::decode_packet(&encoded).unwrap();
assert_eq!(decoded.packet_type, PacketType::Close);
}
+129
View File
@@ -0,0 +1,129 @@
use imks::engine::session::{generate_sid, SessionState, SessionStore, TransportType};
#[test]
fn test_session_store_create_and_get() {
let store = SessionStore::new();
let sid = generate_sid();
let _rx = store.create(sid.clone(), TransportType::Polling);
assert!(store.exists(&sid));
assert!(store.get(&sid).is_some());
assert_eq!(store.len(), 1);
}
#[test]
fn test_session_store_remove() {
let store = SessionStore::new();
let sid = generate_sid();
let _rx = store.create(sid.clone(), TransportType::Polling);
assert!(store.exists(&sid));
store.remove(&sid);
assert!(!store.exists(&sid));
assert!(store.get(&sid).is_none());
}
#[test]
fn test_session_store_multiple_sessions() {
let store = SessionStore::new();
let sid1 = generate_sid();
let sid2 = generate_sid();
let sid3 = generate_sid();
let _rx1 = store.create(sid1.clone(), TransportType::Polling);
let _rx2 = store.create(sid2.clone(), TransportType::WebSocket);
let _rx3 = store.create(sid3.clone(), TransportType::WebTransport);
assert_eq!(store.len(), 3);
assert!(store.exists(&sid1));
assert!(store.exists(&sid2));
assert!(store.exists(&sid3));
}
#[test]
fn test_generate_sid_uniqueness() {
let sids: Vec<String> = (0..100).map(|_| generate_sid()).collect();
let unique_sids: std::collections::HashSet<String> = sids.into_iter().collect();
assert_eq!(unique_sids.len(), 100);
}
#[test]
fn test_generate_sid_format() {
let sid = generate_sid();
assert_eq!(sid.len(), 20);
assert!(sid.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-'));
}
#[tokio::test]
async fn test_session_state_transitions() {
let store = SessionStore::new();
let sid = generate_sid();
let _rx = store.create(sid.clone(), TransportType::Polling);
if let Some(session) = store.get(&sid) {
let mut session = session.write().await;
assert_eq!(session.state, SessionState::Connecting);
session.set_state(SessionState::Open);
assert_eq!(session.state, SessionState::Open);
session.set_state(SessionState::Upgrading);
assert_eq!(session.state, SessionState::Upgrading);
session.set_state(SessionState::Open);
assert_eq!(session.state, SessionState::Open);
session.set_state(SessionState::Closing);
assert_eq!(session.state, SessionState::Closing);
session.set_state(SessionState::Closed);
assert_eq!(session.state, SessionState::Closed);
}
}
#[tokio::test]
async fn test_session_transport_change() {
let store = SessionStore::new();
let sid = generate_sid();
let _rx = store.create(sid.clone(), TransportType::Polling);
if let Some(session) = store.get(&sid) {
let mut session = session.write().await;
assert_eq!(session.transport, TransportType::Polling);
session.set_transport(TransportType::WebSocket);
assert_eq!(session.transport, TransportType::WebSocket);
}
}
#[tokio::test]
async fn test_session_ping_update() {
let store = SessionStore::new();
let sid = generate_sid();
let _rx = store.create(sid.clone(), TransportType::Polling);
if let Some(session) = store.get(&sid) {
let mut session = session.write().await;
let initial_ping = session.last_ping;
std::thread::sleep(std::time::Duration::from_millis(10));
session.update_ping();
assert!(session.last_ping > initial_ping);
}
}
#[test]
fn test_transport_type_as_str() {
assert_eq!(TransportType::Polling.as_str(), "polling");
assert_eq!(TransportType::WebSocket.as_str(), "websocket");
assert_eq!(TransportType::WebTransport.as_str(), "webtransport");
}
+203
View File
@@ -0,0 +1,203 @@
use imks::socket::packet::{Packet, PacketType};
use imks::socket::parser;
use serde_json::json;
#[test]
fn test_socket_io_connect_encoding() {
let packet = Packet::connect("/", None);
let encoded = parser::encode(&packet);
assert_eq!(encoded, "0");
let packet = Packet::connect("/admin", Some(json!({"sid": "abc123"})));
let encoded = parser::encode(&packet);
assert_eq!(encoded, "0/admin,{\"sid\":\"abc123\"}");
}
#[test]
fn test_socket_io_disconnect_encoding() {
let packet = Packet::disconnect("/");
let encoded = parser::encode(&packet);
assert_eq!(encoded, "1");
let packet = Packet::disconnect("/admin");
let encoded = parser::encode(&packet);
assert_eq!(encoded, "1/admin,");
}
#[test]
fn test_socket_io_event_encoding() {
let packet = Packet::event("/", json!(["foo"]), None);
let encoded = parser::encode(&packet);
assert_eq!(encoded, "2[\"foo\"]");
let packet = Packet::event("/admin", json!(["bar"]), None);
let encoded = parser::encode(&packet);
assert_eq!(encoded, "2/admin,[\"bar\"]");
}
#[test]
fn test_socket_io_event_with_ack_encoding() {
let packet = Packet::event("/", json!(["foo"]), Some(12));
let encoded = parser::encode(&packet);
assert_eq!(encoded, "212[\"foo\"]");
let packet = Packet::event("/admin", json!(["bar"]), Some(13));
let encoded = parser::encode(&packet);
assert_eq!(encoded, "2/admin,13[\"bar\"]");
}
#[test]
fn test_socket_io_ack_encoding() {
let packet = Packet::ack("/", json!([]), 12);
let encoded = parser::encode(&packet);
assert_eq!(encoded, "312[]");
let packet = Packet::ack("/admin", json!(["bar"]), 13);
let encoded = parser::encode(&packet);
assert_eq!(encoded, "3/admin,13[\"bar\"]");
}
#[test]
fn test_socket_io_connect_error_encoding() {
let packet = Packet::connect_error("/", "Not authorized");
let encoded = parser::encode(&packet);
assert_eq!(encoded, "4{\"message\":\"Not authorized\"}");
}
#[test]
fn test_socket_io_connect_decoding() {
let packet = parser::decode("0").unwrap();
assert_eq!(packet.packet_type, PacketType::Connect);
assert_eq!(packet.namespace, "/");
assert!(packet.data.is_none());
}
#[test]
fn test_socket_io_connect_with_namespace_decoding() {
let packet = parser::decode("0/admin,{\"sid\":\"abc\"}").unwrap();
assert_eq!(packet.packet_type, PacketType::Connect);
assert_eq!(packet.namespace, "/admin");
assert!(packet.data.is_some());
}
#[test]
fn test_socket_io_disconnect_decoding() {
let packet = parser::decode("1").unwrap();
assert_eq!(packet.packet_type, PacketType::Disconnect);
assert_eq!(packet.namespace, "/");
}
#[test]
fn test_socket_io_disconnect_with_namespace_decoding() {
let packet = parser::decode("1/admin,").unwrap();
assert_eq!(packet.packet_type, PacketType::Disconnect);
assert_eq!(packet.namespace, "/admin");
}
#[test]
fn test_socket_io_event_decoding() {
let packet = parser::decode("2[\"foo\"]").unwrap();
assert_eq!(packet.packet_type, PacketType::Event);
assert_eq!(packet.namespace, "/");
assert_eq!(packet.data, Some(json!(["foo"])));
}
#[test]
fn test_socket_io_event_with_namespace_decoding() {
let packet = parser::decode("2/admin,[\"bar\"]").unwrap();
assert_eq!(packet.packet_type, PacketType::Event);
assert_eq!(packet.namespace, "/admin");
assert_eq!(packet.data, Some(json!(["bar"])));
}
#[test]
fn test_socket_io_event_with_ack_decoding() {
let packet = parser::decode("212[\"foo\"]").unwrap();
assert_eq!(packet.packet_type, PacketType::Event);
assert_eq!(packet.id, Some(12));
assert_eq!(packet.data, Some(json!(["foo"])));
}
#[test]
fn test_socket_io_ack_decoding() {
let packet = parser::decode("312[]").unwrap();
assert_eq!(packet.packet_type, PacketType::Ack);
assert_eq!(packet.id, Some(12));
}
#[test]
fn test_socket_io_connect_error_decoding() {
let packet = parser::decode("4{\"message\":\"Not authorized\"}").unwrap();
assert_eq!(packet.packet_type, PacketType::ConnectError);
assert_eq!(packet.data, Some(json!({"message": "Not authorized"})));
}
#[test]
fn test_socket_io_binary_event_encoding() {
let packet = Packet::binary_event(
"/",
json!(["baz", {"_placeholder": true, "num": 0}]),
None,
vec![vec![0x01, 0x02, 0x03, 0x04]],
);
let encoded = parser::encode(&packet);
assert!(encoded.starts_with("51-"));
}
#[test]
fn test_socket_io_binary_ack_encoding() {
let packet = Packet::binary_ack(
"/",
json!(["bar", {"_placeholder": true, "num": 0}]),
15,
vec![vec![0x01, 0x02, 0x03, 0x04]],
);
let encoded = parser::encode(&packet);
assert!(encoded.starts_with("61-"));
assert!(encoded.contains("15"));
}
#[test]
fn test_socket_io_roundtrip() {
let original = Packet::event("/admin", json!(["hello", "world"]), Some(42));
let encoded = parser::encode(&original);
let decoded = parser::decode(&encoded).unwrap();
assert_eq!(decoded.packet_type, original.packet_type);
assert_eq!(decoded.namespace, original.namespace);
assert_eq!(decoded.id, original.id);
assert_eq!(decoded.data, original.data);
}
#[test]
fn test_socket_io_namespace_handling() {
let namespaces = vec!["/", "/admin", "/chat", "/api/v1"];
for ns in namespaces {
let packet = Packet::event(ns, json!(["test"]), None);
let encoded = parser::encode(&packet);
let decoded = parser::decode(&encoded).unwrap();
assert_eq!(decoded.namespace, ns);
}
}
#[test]
fn test_socket_io_complex_data() {
let complex_data = json!([
"event_name",
{
"user": {
"id": 123,
"name": "test",
"roles": ["admin", "user"]
},
"timestamp": 1234567890
}
]);
let packet = Packet::event("/", complex_data.clone(), None);
let encoded = parser::encode(&packet);
let decoded = parser::decode(&encoded).unwrap();
assert_eq!(decoded.data, Some(complex_data));
}