test(gateway): adapter→session-key integration for Discord + Telegram

Completes the review's ask for "adapter-to-session-key integration coverage
for Discord and a non-Discord platform" on #20096.

Drives a concrete adapter's real BasePlatformAdapter.build_source with an
injected gateway_runner, asserts the matched route's profile is stamped on
the source, and that build_session_key scopes the key under agent:<profile>:
(versus the shared agent:main: namespace). Covers Discord and Telegram — the
Telegram case is the bug-#2 path that previously fell through to default.
Adds a regression anchor: without gateway_runner, profile stays None and the
key lands in agent:main (the silent fallback the fix removes for non-Discord).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Burgunthy 2026-07-14 10:04:02 +09:00 committed by Teknium
parent f58e4622cf
commit c29ab7b9fe

View file

@ -6,9 +6,11 @@ from unittest.mock import MagicMock, patch
import pytest
from gateway.session import SessionSource
from gateway.session import SessionSource, build_session_key
from gateway.run import GatewayRunner
from gateway.profile_routing import ProfileRoute
from gateway.config import Platform
from gateway.platforms.base import BasePlatformAdapter
@pytest.fixture
@ -331,3 +333,89 @@ class TestGatewayRunnerInjection:
# every adapter, not just the ones that pre-declared it (Discord).
assert hasattr(_ToyAdapter, "gateway_runner")
assert _ToyAdapter.gateway_runner is None
# A concrete adapter we can instantiate without the full platform stack.
# ``build_source`` only reads ``self.platform`` and ``self.gateway_runner``, so a
# bare instance with those two attrs exercises the real BasePlatformAdapter
# method end-to-end. Clearing ``__abstractmethods__`` lets ``__new__`` bypass
# the ABC instantiation guard without stubbing connect/send/get_chat_info/…
class _StubAdapter(BasePlatformAdapter):
pass
_StubAdapter.__abstractmethods__ = frozenset() # type: ignore[attr-defined]
def _stub_adapter(platform: Platform, runner) -> "_StubAdapter":
a = _StubAdapter.__new__(_StubAdapter)
a.platform = platform
a.gateway_runner = runner
return a
class TestAdapterToSessionKeyIntegration:
"""Adapter -> ``source.profile`` -> session-key integration coverage.
The review asked for integration coverage for Discord AND a non-Discord
platform. These drive a concrete adapter's real ``build_source``
(BasePlatformAdapter) with an injected ``gateway_runner``, assert the
matched route's profile is stamped on the source, and that the resulting
session key is profile-scoped (``agent:<profile>:...`` rather than the
shared ``agent:main:...``). The Telegram case is the bug-#2 regression:
pre-fix it never received ``gateway_runner`` and fell through to default.
"""
@staticmethod
def _routes():
return [
ProfileRoute(name="dc", platform="discord", profile="coder",
guild_id="111", chat_id="222"),
ProfileRoute(name="tg", platform="telegram", profile="ops",
chat_id="-1001234567890"),
]
def test_discord_adapter_stamps_profile_and_scopes_key(self, mock_runner):
mock_runner.config.profile_routes = self._routes()
adapter = _stub_adapter(Platform.DISCORD, mock_runner)
source = adapter.build_source(
chat_id="222", chat_type="group", guild_id="111", user_id="u1",
)
assert source.profile == "coder"
key = build_session_key(source, profile=source.profile)
assert key.startswith("agent:coder:"), key
# A default-profile key would land in agent:main — must differ.
assert key != build_session_key(source, profile=None)
def test_telegram_adapter_stamps_profile_and_scopes_key(self, mock_runner):
"""Non-Discord platform (bug #2). The adapter now receives
``gateway_runner``, so ``build_source`` stamps the profile and the
session key is isolated under ``agent:ops:`` instead of ``agent:main:``."""
mock_runner.config.profile_routes = self._routes()
adapter = _stub_adapter(Platform.TELEGRAM, mock_runner)
source = adapter.build_source(
chat_id="-1001234567890", chat_type="group", user_id="u1",
)
assert source.profile == "ops"
key = build_session_key(source, profile=source.profile)
assert key.startswith("agent:ops:"), key
assert key != build_session_key(source, profile=None)
def test_adapter_without_runner_falls_back_to_default_namespace(self, mock_runner):
"""Regression anchor: with no ``gateway_runner`` injected (the pre-fix
state for non-Discord adapters), ``build_source`` leaves ``profile=None``
and the session key is the shared ``agent:main:`` namespace no
per-profile isolation. This is the silent fallback the fix removes for
non-Discord platforms."""
adapter = _stub_adapter(Platform.TELEGRAM, runner=None)
source = adapter.build_source(
chat_id="-1001234567890", chat_type="group", user_id="u1",
)
assert source.profile is None
key = build_session_key(source, profile=source.profile)
assert key.startswith("agent:main:"), key