fix(gateway): allow Feishu websocket mode in multiplex profiles
Feishu was unconditionally listed in _PORT_BINDING_PLATFORM_VALUES, causing the multiplexer to reject ALL Feishu secondary profiles. But Feishu in websocket mode (the default) uses an outbound WebSocket connection and does NOT bind an HTTP port — only webhook/callback mode needs a listener. Add _platform_binds_port() helper that checks connection-dependent platforms (currently only Feishu) against their actual config before raising MultiplexConfigError. Feishu websocket profiles are now allowed; Feishu webhook profiles still raise as before. Fixes #52563fix/verification-admin-route-recovery
parent
6b1267c2e4
commit
9cb3569e97
|
|
@ -333,6 +333,29 @@ PORT_BINDING_PLATFORM_VALUES = frozenset({
|
||||||
"line",
|
"line",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Platforms whose port-binding status depends on connection mode. Feishu in
|
||||||
|
# websocket mode (its default) uses an outbound long connection — no listener.
|
||||||
|
# Only webhook/callback mode binds a port. Maps platform value → the mode
|
||||||
|
# value that actually binds (#52563).
|
||||||
|
PORT_BINDING_CONDITIONAL_MODES: dict[str, str] = {
|
||||||
|
"feishu": "webhook",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def platform_binds_port(platform_value: str, extra: Optional[dict] = None) -> bool:
|
||||||
|
"""Return True when *platform_value* actually binds a port for *extra* config.
|
||||||
|
|
||||||
|
Mode-conditional platforms (Feishu) only bind in their listener mode;
|
||||||
|
everything else in ``PORT_BINDING_PLATFORM_VALUES`` always binds.
|
||||||
|
"""
|
||||||
|
if platform_value not in PORT_BINDING_PLATFORM_VALUES:
|
||||||
|
return False
|
||||||
|
expected_mode = PORT_BINDING_CONDITIONAL_MODES.get(platform_value)
|
||||||
|
if expected_mode is not None:
|
||||||
|
actual = str((extra or {}).get("connection_mode", "websocket")).strip().lower()
|
||||||
|
return actual == expected_mode
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HomeChannel:
|
class HomeChannel:
|
||||||
|
|
|
||||||
|
|
@ -1417,6 +1417,7 @@ from contextlib import contextmanager as _contextmanager
|
||||||
# dashboard's pre-write validation enforces the same policy.
|
# dashboard's pre-write validation enforces the same policy.
|
||||||
from gateway.config import (
|
from gateway.config import (
|
||||||
PORT_BINDING_PLATFORM_VALUES as _PORT_BINDING_PLATFORM_VALUES,
|
PORT_BINDING_PLATFORM_VALUES as _PORT_BINDING_PLATFORM_VALUES,
|
||||||
|
platform_binds_port as _platform_binds_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -8802,7 +8803,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||||
port_binding_platforms = sorted(
|
port_binding_platforms = sorted(
|
||||||
platform.value
|
platform.value
|
||||||
for platform, platform_config in profile_cfg.platforms.items()
|
for platform, platform_config in profile_cfg.platforms.items()
|
||||||
if platform_config.enabled and platform.value in _PORT_BINDING_PLATFORM_VALUES
|
if platform_config.enabled
|
||||||
|
and _platform_binds_port(platform.value, platform_config.extra)
|
||||||
)
|
)
|
||||||
if port_binding_platforms:
|
if port_binding_platforms:
|
||||||
joined = ", ".join(port_binding_platforms)
|
joined = ", ".join(port_binding_platforms)
|
||||||
|
|
|
||||||
|
|
@ -530,3 +530,72 @@ class TestSecondaryProfileConfigHandling:
|
||||||
"line",
|
"line",
|
||||||
):
|
):
|
||||||
assert p in _PORT_BINDING_PLATFORM_VALUES
|
assert p in _PORT_BINDING_PLATFORM_VALUES
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestFeishuPortBindingConditional:
|
||||||
|
"""Feishu websocket mode does NOT bind a port; only webhook mode does (#52563)."""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_feishu_websocket_mode_not_rejected(self, monkeypatch):
|
||||||
|
"""Feishu in websocket mode (the default) should NOT raise MultiplexConfigError."""
|
||||||
|
from gateway.run import MultiplexConfigError
|
||||||
|
from gateway.config import GatewayConfig, Platform, PlatformConfig
|
||||||
|
|
||||||
|
runner = GatewayRunner.__new__(GatewayRunner)
|
||||||
|
runner.config = GatewayConfig(multiplex_profiles=True)
|
||||||
|
runner._profile_adapters = {}
|
||||||
|
|
||||||
|
reviewer_cfg = GatewayConfig(multiplex_profiles=True)
|
||||||
|
reviewer_cfg.platforms = {
|
||||||
|
Platform.FEISHU: PlatformConfig(
|
||||||
|
enabled=True,
|
||||||
|
extra={"app_id": "cli_xxx", "app_secret": "sec", "connection_mode": "websocket"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
monkeypatch.setattr("gateway.config.load_gateway_config", lambda: reviewer_cfg)
|
||||||
|
monkeypatch.setattr(runner, "_create_adapter", lambda p, c: None)
|
||||||
|
|
||||||
|
connected = await runner._start_one_profile_adapters("reviewer", "/tmp/x", {})
|
||||||
|
assert connected == 0 # no error, just nothing connected
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_feishu_webhook_mode_raises(self, monkeypatch):
|
||||||
|
"""Feishu in webhook mode binds a port and should raise MultiplexConfigError."""
|
||||||
|
from gateway.run import MultiplexConfigError
|
||||||
|
from gateway.config import GatewayConfig, Platform, PlatformConfig
|
||||||
|
|
||||||
|
runner = GatewayRunner.__new__(GatewayRunner)
|
||||||
|
runner.config = GatewayConfig(multiplex_profiles=True)
|
||||||
|
runner._profile_adapters = {}
|
||||||
|
|
||||||
|
reviewer_cfg = GatewayConfig(multiplex_profiles=True)
|
||||||
|
reviewer_cfg.platforms = {
|
||||||
|
Platform.FEISHU: PlatformConfig(
|
||||||
|
enabled=True,
|
||||||
|
extra={"app_id": "cli_xxx", "app_secret": "sec", "connection_mode": "webhook"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
monkeypatch.setattr("gateway.config.load_gateway_config", lambda: reviewer_cfg)
|
||||||
|
|
||||||
|
with pytest.raises(MultiplexConfigError) as ei:
|
||||||
|
await runner._start_one_profile_adapters("reviewer", "/tmp/x", {})
|
||||||
|
assert "feishu" in str(ei.value)
|
||||||
|
|
||||||
|
def test_platform_binds_port_helper(self):
|
||||||
|
"""Unit test for _platform_binds_port helper."""
|
||||||
|
from gateway.run import _platform_binds_port
|
||||||
|
|
||||||
|
# Non-port-binding platform
|
||||||
|
assert _platform_binds_port("telegram", {}) is False
|
||||||
|
|
||||||
|
# Unconditional port-binding platform
|
||||||
|
assert _platform_binds_port("webhook", {}) is True
|
||||||
|
assert _platform_binds_port("api_server", {}) is True
|
||||||
|
|
||||||
|
# Feishu: websocket = no port binding
|
||||||
|
assert _platform_binds_port("feishu", {"connection_mode": "websocket"}) is False
|
||||||
|
assert _platform_binds_port("feishu", {}) is False # default is websocket
|
||||||
|
|
||||||
|
# Feishu: webhook = port binding
|
||||||
|
assert _platform_binds_port("feishu", {"connection_mode": "webhook"}) is True
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue