fix(gateway): prevent scoped lock and resource leaks on connection failure
This commit is contained in:
@@ -91,6 +91,29 @@ class TestSignalAdapterInit:
|
||||
assert adapter._account_normalized == "+15551234567"
|
||||
|
||||
|
||||
class TestSignalConnectCleanup:
|
||||
"""Regression coverage for failed connect() cleanup."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_releases_lock_and_closes_client_on_healthcheck_failure(self, monkeypatch):
|
||||
adapter = _make_signal_adapter(monkeypatch)
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.get = AsyncMock(return_value=MagicMock(status_code=503))
|
||||
mock_client.aclose = AsyncMock()
|
||||
|
||||
with patch("gateway.platforms.signal.httpx.AsyncClient", return_value=mock_client), \
|
||||
patch("gateway.status.acquire_scoped_lock", return_value=(True, None)), \
|
||||
patch("gateway.status.release_scoped_lock") as mock_release:
|
||||
result = await adapter.connect()
|
||||
|
||||
assert result is False
|
||||
mock_client.aclose.assert_awaited_once()
|
||||
mock_release.assert_called_once_with("signal-phone", "+15551234567")
|
||||
assert adapter.client is None
|
||||
assert adapter._platform_lock_identity is None
|
||||
|
||||
|
||||
class TestSignalHelpers:
|
||||
def test_redact_phone_long(self):
|
||||
from gateway.platforms.helpers import redact_phone
|
||||
|
||||
@@ -150,6 +150,31 @@ class TestAppMentionHandler:
|
||||
assert "/hermes" in registered_commands
|
||||
|
||||
|
||||
class TestSlackConnectCleanup:
|
||||
"""Regression coverage for failed connect() cleanup."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_releases_platform_lock_when_auth_fails(self):
|
||||
config = PlatformConfig(enabled=True, token="xoxb-fake")
|
||||
adapter = SlackAdapter(config)
|
||||
|
||||
mock_app = MagicMock()
|
||||
mock_web_client = AsyncMock()
|
||||
mock_web_client.auth_test = AsyncMock(side_effect=RuntimeError("boom"))
|
||||
|
||||
with patch.object(_slack_mod, "AsyncApp", return_value=mock_app), \
|
||||
patch.object(_slack_mod, "AsyncWebClient", return_value=mock_web_client), \
|
||||
patch.object(_slack_mod, "AsyncSocketModeHandler", return_value=MagicMock()), \
|
||||
patch.dict(os.environ, {"SLACK_APP_TOKEN": "xapp-fake"}), \
|
||||
patch("gateway.status.acquire_scoped_lock", return_value=(True, None)), \
|
||||
patch("gateway.status.release_scoped_lock") as mock_release:
|
||||
result = await adapter.connect()
|
||||
|
||||
assert result is False
|
||||
mock_release.assert_called_once_with("slack-app-token", "xapp-fake")
|
||||
assert adapter._platform_lock_identity is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TestSendDocument
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -211,6 +211,30 @@ class TestFileHandleClosedOnError:
|
||||
assert adapter._bridge_log_fh is None
|
||||
|
||||
|
||||
class TestConnectCleanup:
|
||||
"""Verify failure paths release the scoped session lock."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_releases_lock_when_npm_install_fails(self):
|
||||
adapter = _make_adapter()
|
||||
|
||||
def _path_exists(path_obj):
|
||||
return not str(path_obj).endswith("node_modules")
|
||||
|
||||
install_result = MagicMock(returncode=1, stderr="install failed")
|
||||
|
||||
with patch("gateway.platforms.whatsapp.check_whatsapp_requirements", return_value=True), \
|
||||
patch.object(Path, "exists", autospec=True, side_effect=_path_exists), \
|
||||
patch("subprocess.run", return_value=install_result), \
|
||||
patch("gateway.status.acquire_scoped_lock", return_value=(True, None)), \
|
||||
patch("gateway.status.release_scoped_lock") as mock_release:
|
||||
result = await adapter.connect()
|
||||
|
||||
assert result is False
|
||||
mock_release.assert_called_once_with("whatsapp-session", str(adapter._session_path))
|
||||
assert adapter._platform_lock_identity is None
|
||||
|
||||
|
||||
class TestBridgeRuntimeFailure:
|
||||
"""Verify runtime bridge death is surfaced as a fatal adapter error."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user