"""Regression tests: failed-connect path must call adapter.disconnect(). When adapter.connect() returns False or raises, the adapter may have allocated resources (aiohttp.ClientSession, poll tasks, child subprocesses) before giving up. Without a defensive disconnect() call these leak and surface as "Unclosed client session" warnings at process exit (seen on the 2026-04-18 18:08:16 gateway restart). The fix: gateway/run.py wraps each adapter connect() with a safety-net call to _safe_adapter_disconnect() in the failure branches. """ from unittest.mock import AsyncMock, MagicMock import pytest from gateway.config import Platform from gateway.run import GatewayRunner @pytest.fixture def bare_runner(): """A GatewayRunner shell that only needs to support _safe_adapter_disconnect.""" return object.__new__(GatewayRunner) @pytest.mark.asyncio async def test_safe_disconnect_calls_adapter_disconnect(bare_runner): """The helper forwards to adapter.disconnect().""" adapter = MagicMock() adapter.disconnect = AsyncMock(return_value=None) await bare_runner._safe_adapter_disconnect(adapter, Platform.TELEGRAM) adapter.disconnect.assert_awaited_once() @pytest.mark.asyncio async def test_safe_disconnect_swallows_exceptions(bare_runner): """An exception in adapter.disconnect() must not propagate — the caller is already on an error path.""" adapter = MagicMock() adapter.disconnect = AsyncMock(side_effect=RuntimeError("partial init")) # Must NOT raise await bare_runner._safe_adapter_disconnect(adapter, Platform.TELEGRAM) adapter.disconnect.assert_awaited_once() @pytest.mark.asyncio async def test_safe_disconnect_handles_none_platform(bare_runner): """Logging path must tolerate platform=None.""" adapter = MagicMock() adapter.disconnect = AsyncMock(side_effect=ValueError("nope")) await bare_runner._safe_adapter_disconnect(adapter, None) adapter.disconnect.assert_awaited_once()