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 #52563
This commit is contained in:
parent
6b1267c2e4
commit
9cb3569e97
3 changed files with 95 additions and 1 deletions
|
|
@ -333,6 +333,29 @@ PORT_BINDING_PLATFORM_VALUES = frozenset({
|
|||
"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
|
||||
class HomeChannel:
|
||||
|
|
|
|||
|
|
@ -1417,6 +1417,7 @@ from contextlib import contextmanager as _contextmanager
|
|||
# dashboard's pre-write validation enforces the same policy.
|
||||
from gateway.config import (
|
||||
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(
|
||||
platform.value
|
||||
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:
|
||||
joined = ", ".join(port_binding_platforms)
|
||||
|
|
|
|||
|
|
@ -530,3 +530,72 @@ class TestSecondaryProfileConfigHandling:
|
|||
"line",
|
||||
):
|
||||
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…
Add table
Add a link
Reference in a new issue