"""Unit tests for /handoff platform aliasing over relay (Phase 1 parity). Symptom fixed: on a relay-fronted gateway, ``/handoff discord`` was rejected in TWO places even though "discord" is deliverable through the relay adapter: 1. The CLI pre-check (``cli_commands_mixin._handle_handoff_command``) looked up ``gw_config.platforms.get(DISCORD)`` — only a RELAY entry exists. 2. The gateway watcher (``run.py _process_handoff``) did a literal ``adapters.get(discord)`` — the adapter is registered under RELAY. Both now resolve fronted platforms through the same alias-aware machinery the delivery router uses (``resolve_delivery_transport`` — native adapter wins; relay eligible only when its authenticated transport advertises the logical platform). These tests cover the resolver semantics the watcher depends on, plus the CLI pre-check's env-derived fronted set. """ from __future__ import annotations import pytest from gateway.config import GatewayConfig, Platform, PlatformConfig from gateway.delivery import resolve_delivery_transport class _RelayStub: """Minimal relay adapter double advertising a fronted-platform set.""" def __init__(self, fronted): self._fronted = set(fronted) self.sent = [] def fronts_platform(self, platform): value = getattr(platform, "value", platform) return str(value) in self._fronted async def send_for_platform(self, logical_platform, chat_id, content, reply_to=None, metadata=None): self.sent.append((getattr(logical_platform, "value", logical_platform), chat_id, content, metadata)) return type("R", (), {"success": True, "message_id": "m-1", "error": None})() async def send(self, chat_id, content, reply_to=None, metadata=None): # pragma: no cover raise AssertionError("relay transport must use send_for_platform, not send") def _config_with(platforms): cfg = GatewayConfig() cfg.platforms = platforms return cfg class TestHandoffRelayAliasing: def test_fronted_platform_resolves_to_relay_adapter(self): """The watcher's exact miss: adapters holds only RELAY, target is discord.""" relay = _RelayStub({"discord"}) cfg = _config_with({Platform.RELAY: PlatformConfig(enabled=True)}) transport = resolve_delivery_transport( Platform.DISCORD, cfg, {Platform.RELAY: relay} ) assert transport is not None assert transport.adapter is relay assert transport.is_relay is True class TestCliHandoffFrontedSet: """The CLI pre-check derives the fronted set from deploy env (no live adapter).""" def test_fronted_set_from_env(self, monkeypatch): monkeypatch.setenv("GATEWAY_RELAY_PLATFORMS", "discord,telegram") monkeypatch.delenv("GATEWAY_RELAY_BOT_IDS", raising=False) from gateway.relay import relay_platform_identities fronted = {p for p, _ in relay_platform_identities()} assert "discord" in fronted assert "telegram" in fronted assert "slack" not in fronted