test_safe_adapter_disconnect.py
1 """Regression tests: failed-connect path must call adapter.disconnect(). 2 3 When adapter.connect() returns False or raises, the adapter may have 4 allocated resources (aiohttp.ClientSession, poll tasks, child 5 subprocesses) before giving up. Without a defensive disconnect() call 6 these leak and surface as "Unclosed client session" warnings at 7 process exit (seen on the 2026-04-18 18:08:16 gateway restart). 8 9 The fix: gateway/run.py wraps each adapter connect() with a safety-net 10 call to _safe_adapter_disconnect() in the failure branches. 11 """ 12 13 from unittest.mock import AsyncMock, MagicMock 14 15 import pytest 16 17 from gateway.config import Platform 18 from gateway.run import GatewayRunner 19 20 21 @pytest.fixture 22 def bare_runner(): 23 """A GatewayRunner shell that only needs to support _safe_adapter_disconnect.""" 24 return object.__new__(GatewayRunner) 25 26 27 @pytest.mark.asyncio 28 async def test_safe_disconnect_calls_adapter_disconnect(bare_runner): 29 """The helper forwards to adapter.disconnect().""" 30 adapter = MagicMock() 31 adapter.disconnect = AsyncMock(return_value=None) 32 33 await bare_runner._safe_adapter_disconnect(adapter, Platform.TELEGRAM) 34 35 adapter.disconnect.assert_awaited_once() 36 37 38 @pytest.mark.asyncio 39 async def test_safe_disconnect_swallows_exceptions(bare_runner): 40 """An exception in adapter.disconnect() must not propagate — the 41 caller is already on an error path.""" 42 adapter = MagicMock() 43 adapter.disconnect = AsyncMock(side_effect=RuntimeError("partial init")) 44 45 # Must NOT raise 46 await bare_runner._safe_adapter_disconnect(adapter, Platform.TELEGRAM) 47 48 adapter.disconnect.assert_awaited_once() 49 50 51 @pytest.mark.asyncio 52 async def test_safe_disconnect_handles_none_platform(bare_runner): 53 """Logging path must tolerate platform=None.""" 54 adapter = MagicMock() 55 adapter.disconnect = AsyncMock(side_effect=ValueError("nope")) 56 57 await bare_runner._safe_adapter_disconnect(adapter, None) 58 59 adapter.disconnect.assert_awaited_once()