diff --git a/gateway/platforms/feishu.py b/gateway/platforms/feishu.py index 7fa6e3a9da..6b43ea55cf 100644 --- a/gateway/platforms/feishu.py +++ b/gateway/platforms/feishu.py @@ -951,6 +951,16 @@ def _run_official_feishu_ws_client(ws_client: Any, adapter: Any) -> None: adapter._ws_thread_loop = loop original_connect = ws_client_module.websockets.connect + original_configure = getattr(ws_client, "_configure", None) + + def _apply_runtime_ws_overrides() -> None: + try: + setattr(ws_client, "_reconnect_nonce", adapter._ws_reconnect_nonce) + setattr(ws_client, "_reconnect_interval", adapter._ws_reconnect_interval) + if adapter._ws_ping_interval is not None: + setattr(ws_client, "_ping_interval", adapter._ws_ping_interval) + except Exception: + logger.debug("[Feishu] Failed to apply websocket runtime overrides", exc_info=True) async def _connect_with_overrides(*args: Any, **kwargs: Any) -> Any: if adapter._ws_ping_interval is not None and "ping_interval" not in kwargs: @@ -959,13 +969,23 @@ def _run_official_feishu_ws_client(ws_client: Any, adapter: Any) -> None: kwargs["ping_timeout"] = adapter._ws_ping_timeout return await original_connect(*args, **kwargs) + def _configure_with_overrides(conf: Any) -> Any: + result = original_configure(conf) + _apply_runtime_ws_overrides() + return result + ws_client_module.websockets.connect = _connect_with_overrides + if callable(original_configure): + setattr(ws_client, "_configure", _configure_with_overrides) + _apply_runtime_ws_overrides() try: ws_client.start() except Exception: pass finally: ws_client_module.websockets.connect = original_connect + if callable(original_configure): + setattr(ws_client, "_configure", original_configure) pending = [t for t in asyncio.all_tasks(loop) if not t.done()] for task in pending: task.cancel() @@ -3080,11 +3100,6 @@ class FeishuAdapter(BasePlatformAdapter): event_handler=self._event_handler, domain=domain, ) - try: - setattr(self._ws_client, "_reconnect_nonce", self._ws_reconnect_nonce) - setattr(self._ws_client, "_reconnect_interval", self._ws_reconnect_interval) - except Exception: - logger.debug("[Feishu] Failed to override websocket reconnect settings", exc_info=True) self._ws_future = loop.run_in_executor( None, _run_official_feishu_ws_client, diff --git a/tests/gateway/test_feishu.py b/tests/gateway/test_feishu.py index 64ee36979c..33ed55d3df 100644 --- a/tests/gateway/test_feishu.py +++ b/tests/gateway/test_feishu.py @@ -262,12 +262,21 @@ class TestFeishuAdapterMessaging(unittest.TestCase): with ( patch("gateway.platforms.feishu.FEISHU_AVAILABLE", True), patch("gateway.platforms.feishu.FEISHU_WEBHOOK_AVAILABLE", True), + patch("gateway.platforms.feishu.EventDispatcherHandler") as mock_handler_class, patch("gateway.platforms.feishu.acquire_scoped_lock", return_value=(True, None)), patch("gateway.platforms.feishu.release_scoped_lock"), patch.object(adapter, "_hydrate_bot_identity", new=AsyncMock()), patch.object(adapter, "_build_lark_client", return_value=SimpleNamespace()), patch("gateway.platforms.feishu.web", web_module), ): + mock_builder = Mock() + mock_builder.register_p2_im_message_message_read_v1 = Mock(return_value=mock_builder) + mock_builder.register_p2_im_message_receive_v1 = Mock(return_value=mock_builder) + mock_builder.register_p2_im_message_reaction_created_v1 = Mock(return_value=mock_builder) + mock_builder.register_p2_im_message_reaction_deleted_v1 = Mock(return_value=mock_builder) + mock_builder.register_p2_card_action_trigger = Mock(return_value=mock_builder) + mock_builder.build = Mock(return_value=object()) + mock_handler_class.builder = Mock(return_value=mock_builder) connected = asyncio.run(adapter.connect()) self.assertTrue(connected) @@ -613,6 +622,61 @@ class TestAdapterModule(unittest.TestCase): self.assertIsNone(settings.ws_ping_interval) self.assertIsNone(settings.ws_ping_timeout) + def test_runtime_ws_overrides_reapply_after_sdk_configure(self): + import sys + from types import ModuleType + + class _FakeWSClient: + def __init__(self): + self._reconnect_nonce = 30 + self._reconnect_interval = 120 + self._ping_interval = 120 + self.configure_calls = [] + + def _configure(self, conf): + self.configure_calls.append(conf) + self._reconnect_nonce = conf.ReconnectNonce + self._reconnect_interval = conf.ReconnectInterval + self._ping_interval = conf.PingInterval + + def start(self): + conf = SimpleNamespace(ReconnectNonce=99, ReconnectInterval=88, PingInterval=77) + self._configure(conf) + raise RuntimeError("stop test client") + + fake_client = _FakeWSClient() + fake_adapter = SimpleNamespace( + _ws_thread_loop=None, + _ws_reconnect_nonce=2, + _ws_reconnect_interval=3, + _ws_ping_interval=4, + _ws_ping_timeout=5, + ) + fake_client_module = ModuleType("lark_oapi.ws.client") + fake_client_module.loop = None + fake_client_module.websockets = SimpleNamespace(connect=AsyncMock()) + fake_ws_module = ModuleType("lark_oapi.ws") + fake_ws_module.client = fake_client_module + fake_root_module = ModuleType("lark_oapi") + fake_root_module.ws = fake_ws_module + + original_modules = sys.modules.copy() + sys.modules["lark_oapi"] = fake_root_module + sys.modules["lark_oapi.ws"] = fake_ws_module + sys.modules["lark_oapi.ws.client"] = fake_client_module + try: + from gateway.platforms.feishu import _run_official_feishu_ws_client + + _run_official_feishu_ws_client(fake_client, fake_adapter) + finally: + sys.modules.clear() + sys.modules.update(original_modules) + + self.assertEqual(len(fake_client.configure_calls), 1) + self.assertEqual(fake_client._reconnect_nonce, 2) + self.assertEqual(fake_client._reconnect_interval, 3) + self.assertEqual(fake_client._ping_interval, 4) + class TestAdapterBehavior(unittest.TestCase): @patch.dict(os.environ, {}, clear=True)