Files
hermes-agent/tests/gateway/relay/test_relay_per_platform_caps.py
T
Teknium 39975613b1 test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
Second, deeper pass over tools/gateway/hermes_cli plus first pass over
the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker,
dashboard, conformance, monitoring, secret_sources, hermes_state,
providers). Same rubric as wave 1 (AGENTS.md test policy); security,
alternation/caching invariants, issue-number regressions, and E2E kept.

Real test-quality fixes found and rooted out along the way:
- tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls
  (DEFAULT_CONFIG smart-approval leaked in) — pinned approval
  mode=manual via autouse fixture: 17.4s → 0.4s.
- test_model_switch_custom_providers.py / test_user_providers_model_switch.py
  silently probed live provider catalogs (~2s/test) — stubbed
  cached_provider_model_ids/provider_model_ids/fetch_api_models.
- test_telegram_noise_filter.py: 15-platform copy-paste matrix over
  shared gateway.run logic → 3 representative platforms (55s → 3.9s).
- test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on
  MagicMock agents — interrupt.side_effect now clears _running_agents
  (22s → 1.0s).
- test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x
  (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps
  patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait
  5s → 0.5s.
- test_telegram_init_deadline.py: loop-block margin restored to 1.0s
  with rationale comment — the watchdog-dump assertion needs the loop
  blocked well past deadline+grace under parallel load (flaked once in
  the 40-worker verification run at a 0.2s margin).

Verification: full hermetic suite via scripts/run_tests.sh —
2,438 files, 21,718 tests passed, 0 failed, 293.9s wall.
Suite totals vs original baseline: 46,820 → 19,757 test functions
(−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s.
2026-07-29 13:39:40 -07:00

158 lines
5.8 KiB
Python

"""Per-platform capability descriptors on the relay (multi-platform Phase 1.5).
The bug class: one relay adapter fronts N platforms on one WS, but the
capability surface (``MAX_MESSAGE_LENGTH`` / ``message_len_fn``) was a SCALAR
from whichever descriptor resolved the handshake — so a Discord chat on a
gateway whose primary identity was Telegram inherited Telegram's 4,096-char
cap and over-sent into Discord's 2,000-char API 400 (observed live: 2,543 and
2,641-char sends rejected).
Covers:
- the transport accumulating one descriptor per platform (first = session
default, later frames must NOT overwrite it),
- the map resetting on a re-dial,
- RelayAdapter.max_message_length_for_chat / message_len_fn_for_chat
resolving from the chat's inbound platform,
- fallback to the scalar descriptor for unknown chats / transports without
the map,
- the stream consumer's _raw_message_limit honoring the per-chat cap.
"""
from __future__ import annotations
import asyncio
import json
from typing import Any, Dict, List, Optional
import pytest
from gateway.config import Platform, PlatformConfig
from gateway.platforms.base import MessageEvent, MessageType
from gateway.relay.adapter import RelayAdapter
from gateway.relay.descriptor import CONTRACT_VERSION, CapabilityDescriptor
from gateway.session import SessionSource
from tests.gateway.relay.stub_connector import StubConnector
def _descriptor(platform: str, max_len: int, len_unit: str = "chars") -> CapabilityDescriptor:
return CapabilityDescriptor(
contract_version=CONTRACT_VERSION,
platform=platform,
label=platform.title(),
max_message_length=max_len,
supports_draft_streaming=False,
supports_edit=True,
supports_threads=False,
markdown_dialect="plain",
len_unit=len_unit,
)
DISCORD = _descriptor("discord", 2000)
TELEGRAM = _descriptor("telegram", 4096, len_unit="utf16")
class MultiDescriptorStub(StubConnector):
"""StubConnector extended with the per-platform descriptor map."""
def __init__(self, primary: CapabilityDescriptor, *others: CapabilityDescriptor) -> None:
super().__init__(primary)
self._by_platform = {d.platform: d for d in (primary, *others)}
def descriptor_for_platform(self, platform: str) -> Optional[CapabilityDescriptor]:
return self._by_platform.get(platform)
async def _push(stub: StubConnector, platform: Platform, chat_id: str) -> None:
await stub.push_inbound(
MessageEvent(
text="hi",
message_type=MessageType.TEXT,
source=SessionSource(
platform=platform, chat_id=chat_id, chat_type="dm", user_id="u-1"
),
)
)
# ───────────────────── transport descriptor accumulation ─────────────────────
def _make_transport():
from gateway.relay.ws_transport import WebSocketRelayTransport
return WebSocketRelayTransport(
"wss://connector.example/relay",
"telegram",
"bot-9",
identities=[("telegram", "bot-9"), ("discord", "app-1")],
)
@pytest.mark.asyncio
async def test_transport_descriptor_map_resets_on_redial(monkeypatch):
"""A re-dial starts a fresh handshake generation: stale per-platform
descriptors must not survive into the new connection."""
t = _make_transport()
loop = asyncio.get_running_loop()
t._descriptor_ready = loop.create_future()
await t._handle_frame(json.dumps({"type": "descriptor", "descriptor": DISCORD.__dict__}))
assert t.descriptor_for_platform("discord") is not None
# Simulate _dial_and_start's reset preamble without a real socket.
class _FakeWs:
async def close(self): # pragma: no cover - not called
pass
sent: List[str] = []
async def _fake_connect(url, **kwargs):
return _FakeWs()
async def _fake_send(payload):
sent.append(payload)
import gateway.relay.ws_transport as wst
monkeypatch.setattr(wst, "websockets", type("M", (), {"connect": staticmethod(_fake_connect)}))
monkeypatch.setattr(t, "_send", _fake_send)
monkeypatch.setattr(
t, "_read_loop", lambda: asyncio.sleep(0)
) # substitute a no-op coroutine factory
await t._dial_and_start()
assert t.descriptor_for_platform("discord") is None
assert t._descriptor is None
# ───────────────────── adapter per-chat capability surface ─────────────────────
@pytest.mark.asyncio
async def test_adapter_resolves_per_chat_limits_from_inbound_platform():
"""The live bug shape: primary identity Telegram (4096), a Discord chat on
the same adapter must get Discord's 2000 — not Telegram's scalar."""
stub = MultiDescriptorStub(TELEGRAM, DISCORD)
stub._identities = [("telegram", "bot-9"), ("discord", "app-1")]
adapter = RelayAdapter(PlatformConfig(), TELEGRAM, transport=stub)
await adapter.connect()
await _push(stub, Platform.DISCORD, "dc-1")
await _push(stub, Platform.TELEGRAM, "tg-1")
# Scalar surface still the primary's (back-compat).
assert adapter.MAX_MESSAGE_LENGTH == 4096
# Per-chat: each chat resolves its own platform's cap.
assert adapter.max_message_length_for_chat("dc-1") == 2000
assert adapter.max_message_length_for_chat("tg-1") == 4096
# Length unit follows the chat too: Telegram utf16, Discord codepoints.
surrogate = "\U0001f600" # 2 UTF-16 units, 1 codepoint
assert adapter.message_len_fn_for_chat("tg-1")(surrogate) == 2
assert adapter.message_len_fn_for_chat("dc-1")(surrogate) == 1
# ───────────────────── stream consumer integration ─────────────────────