Files
imks/tests/adapter_tests.rs
T
zhenyi 06e8ee96a5 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
2026-06-10 23:45:40 +08:00

345 lines
12 KiB
Rust

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)));
}