hermes-agent/tests/gateway/relay/test_descriptor.py
Ben Barclay ebab890ae5
feat(relay): Phase 1 parity — supported_ops discovery, wire identity fields, /handoff aliasing, provision displayName (#71300)
- CapabilityDescriptor.supported_ops + supports_op() with legacy-op-set
  fallback (additive, contract doc §2 updated)
- get_chat_info gated on op discovery (skip round trip on legacy connectors)
- _event_from_wire consumes user_display_name/user_handle (§3 fields
  previously dropped); native display-name parity, session-key stable
- /handoff <fronted-platform> works on relay-fronted gateways: CLI pre-check
  reads the GATEWAY_RELAY_PLATFORMS fronted set; watcher resolves through
  resolve_delivery_transport and replies via send_for_platform
- self-provision forwards displayName (env > skin branding, stock brand
  suppressed) — the primary name source for gg#171 attribution
2026-07-25 20:36:16 +10:00

140 lines
5.5 KiB
Python

"""Tests for the experimental CapabilityDescriptor (relay Phase 0, Task 0.2)."""
from gateway.relay.descriptor import CONTRACT_VERSION, CapabilityDescriptor
def _telegram_descriptor(**overrides) -> CapabilityDescriptor:
base = dict(
contract_version=CONTRACT_VERSION,
platform="telegram",
label="Telegram",
max_message_length=4096,
supports_draft_streaming=False,
supports_edit=True,
supports_threads=True,
markdown_dialect="markdown_v2",
len_unit="utf16",
emoji="\u2708\ufe0f",
platform_hint="You are on Telegram.",
pii_safe=False,
)
base.update(overrides)
return CapabilityDescriptor(**base)
def test_descriptor_roundtrips_json():
d = _telegram_descriptor()
assert CapabilityDescriptor.from_json(d.to_json()) == d
def test_descriptor_is_frozen():
d = _telegram_descriptor()
try:
d.max_message_length = 1 # type: ignore[misc]
except Exception as exc: # FrozenInstanceError
assert "cannot assign" in str(exc) or "frozen" in str(exc).lower()
else: # pragma: no cover
raise AssertionError("descriptor should be immutable (frozen)")
def test_from_json_ignores_unknown_keys():
"""A newer connector may send fields this gateway doesn't know — those are
dropped, not fatal (forward-compat during the experimental phase)."""
d = _telegram_descriptor()
raw = d.to_json()[:-1] + ', "future_field": "ignored"}'
restored = CapabilityDescriptor.from_json(raw)
assert restored == d
def test_from_json_normalizes_zero_max_message_length_to_default():
"""A connector may advertise max_message_length 0 ("no limit"). from_json
must normalize it to the documented 4096 default so the receiver never
carries a degenerate chunking bound into truncate_message()."""
raw = (
'{"contract_version": 1, "platform": "x", "label": "X", '
'"max_message_length": 0, "supports_draft_streaming": false, '
'"supports_edit": false, "supports_threads": false, '
'"markdown_dialect": "plain", "len_unit": "chars"}'
)
d = CapabilityDescriptor.from_json(raw)
assert d.max_message_length == 4096
def test_from_json_normalizes_negative_max_message_length_to_default():
"""A buggy/hostile connector sending a negative bound is normalized too."""
raw = (
'{"contract_version": 1, "platform": "x", "label": "X", '
'"max_message_length": -5, "supports_draft_streaming": false, '
'"supports_edit": false, "supports_threads": false, '
'"markdown_dialect": "plain", "len_unit": "chars"}'
)
d = CapabilityDescriptor.from_json(raw)
assert d.max_message_length == 4096
def test_from_json_keeps_a_real_positive_bound():
"""A normal positive bound is passed through unchanged."""
d = CapabilityDescriptor.from_json(_telegram_descriptor(max_message_length=2000).to_json())
assert d.max_message_length == 2000
def test_from_json_fills_optional_defaults():
"""Optional fields (emoji/platform_hint/pii_safe) fall back to defaults."""
minimal = (
'{"contract_version": 1, "platform": "x", "label": "X", '
'"max_message_length": 2000, "supports_draft_streaming": false, '
'"supports_edit": false, "supports_threads": false, '
'"markdown_dialect": "plain", "len_unit": "chars"}'
)
d = CapabilityDescriptor.from_json(minimal)
assert d.pii_safe is False
assert d.platform_hint == ""
assert d.emoji == "\U0001f50c"
def test_module_is_marked_experimental():
import gateway.relay.descriptor as m
assert "EXPERIMENTAL" in (m.__doc__ or "")
# ─────────────── supported_ops (op-level capability discovery, Phase 1) ───────────────
def test_supported_ops_roundtrips_json():
d = _telegram_descriptor(supported_ops=("send", "edit", "typing"))
restored = CapabilityDescriptor.from_json(d.to_json())
assert restored.supported_ops == ("send", "edit", "typing")
assert restored == d
def test_supports_op_advertised_list_is_authoritative():
d = _telegram_descriptor(supported_ops=("send", "typing", "get_chat_info"))
assert d.supports_op("send") is True
assert d.supports_op("get_chat_info") is True
# An advertised list EXCLUDES what it omits — even a legacy op.
assert d.supports_op("edit") is False
def test_supports_op_legacy_connector_assumes_legacy_set():
"""An empty supported_ops means the connector predates op discovery: the
legacy four ops are assumed supported (old connectors keep working), while
NEW ops are not (discovery semantics — never probe by trying)."""
d = _telegram_descriptor() # no supported_ops
for op in ("send", "edit", "typing", "follow_up"):
assert d.supports_op(op) is True, op
assert d.supports_op("get_chat_info") is False
def test_from_json_normalizes_malformed_supported_ops():
"""Non-list shapes and non-string members degrade to the legacy fallback
(empty tuple), never raise — malformed input can't break the handshake."""
base = (
'{"contract_version": 1, "platform": "x", "label": "X", '
'"max_message_length": 2000, "supports_draft_streaming": false, '
'"supports_edit": true, "supports_threads": false, '
'"markdown_dialect": "plain", "len_unit": "chars", '
)
d = CapabilityDescriptor.from_json(base + '"supported_ops": "send"}')
assert d.supported_ops == ()
d = CapabilityDescriptor.from_json(base + '"supported_ops": ["send", 7, null, ""]}')
assert d.supported_ops == ("send",)