//! df-relay 单元测试(任务 #12) //! //! 覆盖纯逻辑(不依赖 axum server): //! - ConnRegistry: add/remove/is_online/route(四种路由规则) //! - BroadcastMessage 构造器 //! - RelayState async 包装 //! - next_conn_id 单调递增 #![cfg(test)] use df_relay::{ next_conn_id, BroadcastMessage, ClientKind, ConnHandle, ConnId, ConnRegistry, MessageKind, RelayState, }; use tokio::sync::mpsc; // ---------- ConnId 分配 ---------- #[test] fn next_conn_id_monotonic() { let a = next_conn_id(); let b = next_conn_id(); let c = next_conn_id(); assert!(b.0 > a.0, "next_conn_id 应单调递增"); assert!(c.0 > b.0); } #[test] fn conn_id_nil_is_zero() { assert_eq!(ConnId::NIL.0, 0); assert_ne!(next_conn_id(), ConnId::NIL, "分配的 id 不应为 NIL(0)"); } // ---------- ConnRegistry 基础操作 ---------- fn make_handle(id: ConnId, kind: ClientKind, device_id: &str) -> (ConnHandle, mpsc::UnboundedReceiver) { let (tx, rx) = mpsc::unbounded_channel(); (ConnHandle::new(id, kind, device_id.to_string(), tx), rx) } /// 注册连接并返回 receiver(保活防 channel 关闭)。 fn register(reg: &mut ConnRegistry, id: ConnId, kind: ClientKind, device_id: &str) -> mpsc::UnboundedReceiver { let (handle, rx) = make_handle(id, kind, device_id); reg.add(handle); rx } #[test] fn registry_add_and_lookup() { let mut reg = ConnRegistry::new(); let id = next_conn_id(); let _rx = register(&mut reg, id, ClientKind::Device, "dev-1"); assert_eq!(reg.len(), 1); assert!(reg.is_device_online("dev-1")); assert!(!reg.is_device_online("dev-2")); } #[test] fn registry_remove_returns_device_id() { let mut reg = ConnRegistry::new(); let id = next_conn_id(); let _rx = register(&mut reg, id, ClientKind::Device, "dev-1"); let removed = reg.remove(id); assert_eq!(removed.as_deref(), Some("dev-1")); assert_eq!(reg.len(), 0); assert!(!reg.is_device_online("dev-1")); } #[test] fn registry_remove_unknown_returns_none() { let mut reg = ConnRegistry::new(); assert_eq!(reg.remove(ConnId(9999)), None); } #[test] fn registry_supports_multi_conn_per_device() { let mut reg = ConnRegistry::new(); let d1 = next_conn_id(); let m1 = next_conn_id(); let _r1 = register(&mut reg, d1, ClientKind::Device, "dev-1"); let _r2 = register(&mut reg, m1, ClientKind::Miniapp, "dev-1"); assert_eq!(reg.len(), 2); assert!(reg.is_device_online("dev-1")); reg.remove(d1); assert!(reg.is_device_online("dev-1")); reg.remove(m1); assert!(!reg.is_device_online("dev-1")); } #[test] fn registry_empty_default() { let reg = ConnRegistry::new(); assert!(reg.is_empty()); assert_eq!(reg.len(), 0); } // ---------- 路由规则 ---------- #[test] fn route_event_device_to_miniapp_only() { let mut reg = ConnRegistry::new(); let dev = next_conn_id(); let mini = next_conn_id(); let other_mini = next_conn_id(); let _r1 = register(&mut reg, dev, ClientKind::Device, "dev-1"); let _r2 = register(&mut reg, mini, ClientKind::Miniapp, "dev-1"); let _r3 = register(&mut reg, other_mini, ClientKind::Miniapp, "dev-2"); let msg = BroadcastMessage::from_device("dev-1", dev, serde_json::json!({}), 0); let delivered = reg.route(&msg); assert_eq!(delivered, 1, "Event 应只投递给 dev-1 的小程序"); } #[test] fn route_command_miniapp_to_device_only() { let mut reg = ConnRegistry::new(); let dev = next_conn_id(); let mini = next_conn_id(); let _r1 = register(&mut reg, dev, ClientKind::Device, "dev-1"); let _r2 = register(&mut reg, mini, ClientKind::Miniapp, "dev-1"); let msg = BroadcastMessage { device_id: "dev-1".into(), kind: MessageKind::Command, source: mini, from: ClientKind::Miniapp, payload: serde_json::json!({}), ts: 0, }; let delivered = reg.route(&msg); assert_eq!(delivered, 1, "Command 应只投递给 dev-1 的桌面端"); } #[test] fn route_does_not_loop_back_to_source() { let mut reg = ConnRegistry::new(); let dev = next_conn_id(); let _r1 = register(&mut reg, dev, ClientKind::Device, "dev-1"); let msg = BroadcastMessage::from_device("dev-1", dev, serde_json::json!({}), 0); let delivered = reg.route(&msg); assert_eq!(delivered, 0, "不应回环给来源"); } #[test] fn route_control_to_all_except_source() { let mut reg = ConnRegistry::new(); let dev = next_conn_id(); let mini1 = next_conn_id(); let mini2 = next_conn_id(); let _r1 = register(&mut reg, dev, ClientKind::Device, "dev-1"); let _r2 = register(&mut reg, mini1, ClientKind::Miniapp, "dev-1"); let _r3 = register(&mut reg, mini2, ClientKind::Miniapp, "dev-1"); let msg = BroadcastMessage { device_id: "dev-1".into(), kind: MessageKind::Control, source: dev, from: ClientKind::Device, payload: serde_json::json!({}), ts: 0, }; let delivered = reg.route(&msg); assert_eq!(delivered, 2, "Control 应投递给除来源外的所有同 device 连接"); } #[test] fn route_unknown_device_zero() { let reg = ConnRegistry::new(); let msg = BroadcastMessage::from_device("nope", ConnId::NIL, serde_json::json!({}), 0); assert_eq!(reg.route(&msg), 0); } #[test] fn route_event_not_delivered_to_device() { let mut reg = ConnRegistry::new(); let dev1 = next_conn_id(); let dev2 = next_conn_id(); let _r1 = register(&mut reg, dev1, ClientKind::Device, "dev-1"); let _r2 = register(&mut reg, dev2, ClientKind::Device, "dev-1"); let msg = BroadcastMessage::from_device("dev-1", dev1, serde_json::json!({}), 0); assert_eq!(reg.route(&msg), 0, "两个 Device 互发 Event 不应投递"); } // ---------- BroadcastMessage 构造器 ---------- #[test] fn from_device_sets_event_kind_and_from() { let msg = BroadcastMessage::from_device("dev-1", ConnId(42), serde_json::json!({"a": 1}), 12345); assert_eq!(msg.device_id, "dev-1"); assert_eq!(msg.kind, MessageKind::Event); assert_eq!(msg.from, ClientKind::Device); assert_eq!(msg.source, ConnId(42)); assert_eq!(msg.ts, 12345); assert_eq!(msg.payload, serde_json::json!({"a": 1})); } // ---------- RelayState async 包装 ---------- #[tokio::test] async fn relay_state_add_and_remove_conn() { let state = RelayState::new(); let id = next_conn_id(); let (handle, _rx) = make_handle(id, ClientKind::Device, "dev-1"); state.add_conn(handle).await; assert!(state.is_device_online("dev-1").await); let removed = state.remove_conn(id).await; assert_eq!(removed.as_deref(), Some("dev-1")); assert!(!state.is_device_online("dev-1").await); } #[tokio::test] async fn relay_state_route_async() { let state = RelayState::new(); let dev = next_conn_id(); let mini = next_conn_id(); let (h1, _r1) = make_handle(dev, ClientKind::Device, "dev-1"); let (h2, _r2) = make_handle(mini, ClientKind::Miniapp, "dev-1"); state.add_conn(h1).await; state.add_conn(h2).await; let msg = BroadcastMessage::from_device("dev-1", dev, serde_json::json!({}), 0); let n = state.route(&msg).await; assert_eq!(n, 1); }