fix(gateway): validate multiplex adapter config by platform
parent
bd44ef8645
commit
64746b4bd3
|
|
@ -236,8 +236,16 @@ def _looks_like_e164_number(value: str) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def check_signal_requirements() -> bool:
|
def check_signal_requirements() -> bool:
|
||||||
"""Check if Signal is configured (has URL and account)."""
|
"""Check if Signal runtime dependencies are available."""
|
||||||
return bool(os.getenv("SIGNAL_HTTP_URL") and os.getenv("SIGNAL_ACCOUNT"))
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_signal_config(config: PlatformConfig) -> bool:
|
||||||
|
"""Check if Signal has enough config to connect."""
|
||||||
|
extra = getattr(config, "extra", {}) or {}
|
||||||
|
http_url = (extra.get("http_url", "") or os.getenv("SIGNAL_HTTP_URL", "")).strip()
|
||||||
|
account = (extra.get("account", "") or os.getenv("SIGNAL_ACCOUNT", "")).strip()
|
||||||
|
return bool(http_url and account)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -9018,8 +9018,15 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||||
return WhatsAppCloudAdapter(config)
|
return WhatsAppCloudAdapter(config)
|
||||||
|
|
||||||
elif platform == Platform.SIGNAL:
|
elif platform == Platform.SIGNAL:
|
||||||
from gateway.platforms.signal import SignalAdapter, check_signal_requirements
|
from gateway.platforms.signal import (
|
||||||
|
SignalAdapter,
|
||||||
|
check_signal_requirements,
|
||||||
|
validate_signal_config,
|
||||||
|
)
|
||||||
if not check_signal_requirements():
|
if not check_signal_requirements():
|
||||||
|
logger.warning("Signal: runtime requirements not met")
|
||||||
|
return None
|
||||||
|
if not validate_signal_config(config):
|
||||||
logger.warning("Signal: SIGNAL_HTTP_URL or SIGNAL_ACCOUNT not configured")
|
logger.warning("Signal: SIGNAL_HTTP_URL or SIGNAL_ACCOUNT not configured")
|
||||||
return None
|
return None
|
||||||
return SignalAdapter(config)
|
return SignalAdapter(config)
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,13 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def check_ha_requirements() -> bool:
|
def check_ha_requirements() -> bool:
|
||||||
"""Check if Home Assistant runtime dependencies are available."""
|
"""Check if Home Assistant runtime dependencies are available."""
|
||||||
return bool(AIOHTTP_AVAILABLE)
|
return AIOHTTP_AVAILABLE
|
||||||
|
|
||||||
|
|
||||||
def validate_ha_config(config: PlatformConfig) -> bool:
|
def validate_ha_config(config: PlatformConfig) -> bool:
|
||||||
"""Accept either a scoped config token or an env-provided HASS_TOKEN."""
|
"""Return True when Home Assistant has enough credential config to connect."""
|
||||||
token = getattr(config, "token", None) or os.getenv("HASS_TOKEN", "")
|
token = (getattr(config, "token", None) or os.getenv("HASS_TOKEN", "")).strip()
|
||||||
return bool(str(token or "").strip())
|
return bool(token)
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantAdapter(BasePlatformAdapter):
|
class HomeAssistantAdapter(BasePlatformAdapter):
|
||||||
|
|
|
||||||
|
|
@ -51,15 +51,7 @@ _RECONNECT_JITTER = 0.2
|
||||||
|
|
||||||
|
|
||||||
def check_mattermost_requirements() -> bool:
|
def check_mattermost_requirements() -> bool:
|
||||||
"""Return True if the Mattermost adapter can be used."""
|
"""Return True if the Mattermost adapter runtime dependency is available."""
|
||||||
token = os.getenv("MATTERMOST_TOKEN", "")
|
|
||||||
url = os.getenv("MATTERMOST_URL", "")
|
|
||||||
if not token:
|
|
||||||
logger.debug("Mattermost: MATTERMOST_TOKEN not set")
|
|
||||||
return False
|
|
||||||
if not url:
|
|
||||||
logger.warning("Mattermost: MATTERMOST_URL not set")
|
|
||||||
return False
|
|
||||||
try:
|
try:
|
||||||
import aiohttp # noqa: F401
|
import aiohttp # noqa: F401
|
||||||
return True
|
return True
|
||||||
|
|
@ -68,6 +60,20 @@ def check_mattermost_requirements() -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_mattermost_config(config: PlatformConfig) -> bool:
|
||||||
|
"""Return True when Mattermost has enough config to connect."""
|
||||||
|
extra = getattr(config, "extra", {}) or {}
|
||||||
|
token = (getattr(config, "token", None) or os.getenv("MATTERMOST_TOKEN", "")).strip()
|
||||||
|
url = (extra.get("url", "") or os.getenv("MATTERMOST_URL", "")).strip()
|
||||||
|
if not token:
|
||||||
|
logger.debug("Mattermost: MATTERMOST_TOKEN not set")
|
||||||
|
return False
|
||||||
|
if not url:
|
||||||
|
logger.warning("Mattermost: MATTERMOST_URL not set")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class MattermostAdapter(BasePlatformAdapter):
|
class MattermostAdapter(BasePlatformAdapter):
|
||||||
"""Gateway adapter for Mattermost (self-hosted or cloud)."""
|
"""Gateway adapter for Mattermost (self-hosted or cloud)."""
|
||||||
|
|
||||||
|
|
@ -1248,6 +1254,7 @@ def register(ctx) -> None:
|
||||||
label="Mattermost",
|
label="Mattermost",
|
||||||
adapter_factory=_build_adapter,
|
adapter_factory=_build_adapter,
|
||||||
check_fn=check_mattermost_requirements,
|
check_fn=check_mattermost_requirements,
|
||||||
|
validate_config=validate_mattermost_config,
|
||||||
is_connected=_is_connected,
|
is_connected=_is_connected,
|
||||||
required_env=["MATTERMOST_URL", "MATTERMOST_TOKEN"],
|
required_env=["MATTERMOST_URL", "MATTERMOST_TOKEN"],
|
||||||
install_hint="pip install aiohttp",
|
install_hint="pip install aiohttp",
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,29 @@ from plugins.platforms.homeassistant.adapter import (
|
||||||
|
|
||||||
|
|
||||||
class TestCheckRequirements:
|
class TestCheckRequirements:
|
||||||
def test_returns_true_when_aiohttp_present(self, monkeypatch):
|
def test_returns_true_without_token_when_aiohttp_available(self, monkeypatch):
|
||||||
monkeypatch.delenv("HASS_TOKEN", raising=False)
|
monkeypatch.delenv("HASS_TOKEN", raising=False)
|
||||||
assert check_ha_requirements() is True
|
assert check_ha_requirements() is True
|
||||||
|
|
||||||
|
def test_returns_true_with_token(self, monkeypatch):
|
||||||
|
monkeypatch.setenv("HASS_TOKEN", "test-token")
|
||||||
|
assert check_ha_requirements() is True
|
||||||
|
|
||||||
@patch("plugins.platforms.homeassistant.adapter.AIOHTTP_AVAILABLE", False)
|
@patch("plugins.platforms.homeassistant.adapter.AIOHTTP_AVAILABLE", False)
|
||||||
def test_returns_false_without_aiohttp(self, monkeypatch):
|
def test_returns_false_without_aiohttp(self, monkeypatch):
|
||||||
monkeypatch.setenv("HASS_TOKEN", "test-token")
|
monkeypatch.setenv("HASS_TOKEN", "test-token")
|
||||||
assert check_ha_requirements() is False
|
assert check_ha_requirements() is False
|
||||||
|
|
||||||
|
def test_validate_config_accepts_platform_token(self, monkeypatch):
|
||||||
|
monkeypatch.delenv("HASS_TOKEN", raising=False)
|
||||||
|
config = PlatformConfig(enabled=True, token="config-token")
|
||||||
|
assert validate_ha_config(config) is True
|
||||||
|
|
||||||
|
def test_validate_config_rejects_missing_token(self, monkeypatch):
|
||||||
|
monkeypatch.delenv("HASS_TOKEN", raising=False)
|
||||||
|
config = PlatformConfig(enabled=True, token="")
|
||||||
|
assert validate_ha_config(config) is False
|
||||||
|
|
||||||
|
|
||||||
class TestValidateConfig:
|
class TestValidateConfig:
|
||||||
def test_returns_false_without_token_in_config_or_env(self, monkeypatch):
|
def test_returns_false_without_token_in_config_or_env(self, monkeypatch):
|
||||||
|
|
|
||||||
|
|
@ -916,13 +916,32 @@ class TestMattermostRequirements:
|
||||||
monkeypatch.delenv("MATTERMOST_TOKEN", raising=False)
|
monkeypatch.delenv("MATTERMOST_TOKEN", raising=False)
|
||||||
monkeypatch.delenv("MATTERMOST_URL", raising=False)
|
monkeypatch.delenv("MATTERMOST_URL", raising=False)
|
||||||
from plugins.platforms.mattermost.adapter import check_mattermost_requirements
|
from plugins.platforms.mattermost.adapter import check_mattermost_requirements
|
||||||
assert check_mattermost_requirements() is False
|
assert check_mattermost_requirements() is True
|
||||||
|
|
||||||
def test_check_requirements_without_url(self, monkeypatch):
|
def test_check_requirements_without_url(self, monkeypatch):
|
||||||
monkeypatch.setenv("MATTERMOST_TOKEN", "test-token")
|
monkeypatch.setenv("MATTERMOST_TOKEN", "test-token")
|
||||||
monkeypatch.delenv("MATTERMOST_URL", raising=False)
|
monkeypatch.delenv("MATTERMOST_URL", raising=False)
|
||||||
from plugins.platforms.mattermost.adapter import check_mattermost_requirements
|
from plugins.platforms.mattermost.adapter import check_mattermost_requirements
|
||||||
assert check_mattermost_requirements() is False
|
assert check_mattermost_requirements() is True
|
||||||
|
|
||||||
|
def test_validate_config_accepts_platform_values(self, monkeypatch):
|
||||||
|
monkeypatch.delenv("MATTERMOST_TOKEN", raising=False)
|
||||||
|
monkeypatch.delenv("MATTERMOST_URL", raising=False)
|
||||||
|
from plugins.platforms.mattermost.adapter import validate_mattermost_config
|
||||||
|
|
||||||
|
config = PlatformConfig(
|
||||||
|
enabled=True,
|
||||||
|
token="cfg-token",
|
||||||
|
extra={"url": "https://mm.example.com"},
|
||||||
|
)
|
||||||
|
assert validate_mattermost_config(config) is True
|
||||||
|
|
||||||
|
def test_validate_config_rejects_missing_url(self, monkeypatch):
|
||||||
|
monkeypatch.delenv("MATTERMOST_URL", raising=False)
|
||||||
|
from plugins.platforms.mattermost.adapter import validate_mattermost_config
|
||||||
|
|
||||||
|
config = PlatformConfig(enabled=True, token="cfg-token", extra={})
|
||||||
|
assert validate_mattermost_config(config) is False
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -302,7 +302,29 @@ class TestSignalHelpers:
|
||||||
from gateway.platforms.signal import check_signal_requirements
|
from gateway.platforms.signal import check_signal_requirements
|
||||||
monkeypatch.delenv("SIGNAL_HTTP_URL", raising=False)
|
monkeypatch.delenv("SIGNAL_HTTP_URL", raising=False)
|
||||||
monkeypatch.delenv("SIGNAL_ACCOUNT", raising=False)
|
monkeypatch.delenv("SIGNAL_ACCOUNT", raising=False)
|
||||||
assert check_signal_requirements() is False
|
assert check_signal_requirements() is True
|
||||||
|
|
||||||
|
def test_validate_signal_config_accepts_platform_values(self, monkeypatch):
|
||||||
|
monkeypatch.delenv("SIGNAL_HTTP_URL", raising=False)
|
||||||
|
monkeypatch.delenv("SIGNAL_ACCOUNT", raising=False)
|
||||||
|
from gateway.platforms.signal import validate_signal_config
|
||||||
|
|
||||||
|
config = PlatformConfig(
|
||||||
|
enabled=True,
|
||||||
|
extra={
|
||||||
|
"http_url": "http://localhost:8080",
|
||||||
|
"account": "+155****4567",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert validate_signal_config(config) is True
|
||||||
|
|
||||||
|
def test_validate_signal_config_rejects_missing_account(self, monkeypatch):
|
||||||
|
monkeypatch.delenv("SIGNAL_HTTP_URL", raising=False)
|
||||||
|
monkeypatch.delenv("SIGNAL_ACCOUNT", raising=False)
|
||||||
|
from gateway.platforms.signal import validate_signal_config
|
||||||
|
|
||||||
|
config = PlatformConfig(enabled=True, extra={"http_url": "http://localhost:8080"})
|
||||||
|
assert validate_signal_config(config) is False
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue