use std::collections::HashSet; use std::sync::Arc; use imks::socket::adapter::{ Adapter, AdapterError, BroadcastFlags, BroadcastOptions, 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> = 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_default() .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>> = 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_default() .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>> = 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_default() .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>> = 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_default() .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) )); }