4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
256 lines
8.9 KiB
Python
256 lines
8.9 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from collections.abc import Iterator
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from gateway.config.get_gateway_settings import (
|
|
GatewayConfigurationError,
|
|
GatewayEnv,
|
|
GatewaySettings,
|
|
choose_authorized_users,
|
|
choose_bot_token,
|
|
load_gateway_settings,
|
|
load_telegram_credentials,
|
|
store_allowed_users,
|
|
store_bot_token,
|
|
try_load_gateway_settings_for_startup,
|
|
)
|
|
from integrations.messaging_security import MessagingIdentityPolicy
|
|
|
|
_STORE_PATH = "gateway.config.get_gateway_settings.get_integration"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clean_env(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
|
"""Remove all TELEGRAM_* env vars so GatewayEnv falls back to defaults.
|
|
|
|
The root conftest loads a local ``.env`` into ``os.environ``; without this
|
|
the gateway env settings would be non-deterministic across machines.
|
|
"""
|
|
for key in list(os.environ):
|
|
if key.startswith("TELEGRAM_"):
|
|
monkeypatch.delenv(key, raising=False)
|
|
yield
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GatewayEnv
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_gateway_env_defaults() -> None:
|
|
env = GatewayEnv()
|
|
assert env.bot_token == ""
|
|
assert env.allowed_users == []
|
|
assert env.gateway_max_concurrent == 4
|
|
assert env.gateway_auto_start is True
|
|
|
|
|
|
def test_gateway_env_auto_start_can_be_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("TELEGRAM_GATEWAY_AUTO_START", "false")
|
|
env = GatewayEnv()
|
|
assert env.gateway_auto_start is False
|
|
|
|
|
|
def test_gateway_env_reads_prefixed_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "tok")
|
|
monkeypatch.setenv("TELEGRAM_GATEWAY_MAX_CONCURRENT", "8")
|
|
env = GatewayEnv()
|
|
assert env.bot_token == "tok"
|
|
assert env.gateway_max_concurrent == 8
|
|
|
|
|
|
def test_gateway_env_parses_allowed_users_csv(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setenv("TELEGRAM_ALLOWED_USERS", " 42, 99 ,, 7 ")
|
|
env = GatewayEnv()
|
|
assert env.allowed_users == ["42", "99", "7"]
|
|
|
|
|
|
def test_load_gateway_settings_maps_auto_start(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "tok")
|
|
monkeypatch.setenv("TELEGRAM_GATEWAY_AUTO_START", "off")
|
|
settings = load_gateway_settings()
|
|
assert settings.auto_start_enabled is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# load_telegram_credentials
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_load_credentials_returns_credentials_mapping() -> None:
|
|
record = {"credentials": {"bot_token": "from-store"}}
|
|
with patch(_STORE_PATH, return_value=record):
|
|
assert load_telegram_credentials() == {"bot_token": "from-store"}
|
|
|
|
|
|
def test_load_credentials_no_record_returns_empty() -> None:
|
|
with patch(_STORE_PATH, return_value=None):
|
|
assert load_telegram_credentials() == {}
|
|
|
|
|
|
def test_load_credentials_no_credentials_key_returns_empty() -> None:
|
|
with patch(_STORE_PATH, return_value={"name": "telegram"}):
|
|
assert load_telegram_credentials() == {}
|
|
|
|
|
|
def test_load_credentials_store_failure_raises() -> None:
|
|
with (
|
|
patch(_STORE_PATH, side_effect=RuntimeError("boom")),
|
|
pytest.raises(GatewayConfigurationError, match="Could not load Telegram"),
|
|
):
|
|
load_telegram_credentials()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# store_bot_token / store_allowed_users
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_store_bot_token_strips_value() -> None:
|
|
assert store_bot_token({"bot_token": " tok "}) == "tok"
|
|
|
|
|
|
def test_store_bot_token_missing_returns_empty() -> None:
|
|
assert store_bot_token({}) == ""
|
|
|
|
|
|
def test_store_allowed_users_no_policy_returns_empty() -> None:
|
|
assert store_allowed_users({}) == []
|
|
|
|
|
|
def test_store_allowed_users_reads_policy_ids() -> None:
|
|
policy = MessagingIdentityPolicy(allowed_user_ids=["42", "99"]).model_dump()
|
|
assert store_allowed_users({"identity_policy": policy}) == ["42", "99"]
|
|
|
|
|
|
def test_store_allowed_users_non_mapping_policy_raises() -> None:
|
|
with pytest.raises(GatewayConfigurationError, match="must be an object"):
|
|
store_allowed_users({"identity_policy": "nope"})
|
|
|
|
|
|
def test_store_allowed_users_invalid_policy_raises() -> None:
|
|
with pytest.raises(GatewayConfigurationError, match="Invalid Telegram identity_policy"):
|
|
store_allowed_users({"identity_policy": {"allowed_user_ids": "not-a-list"}})
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# choose_bot_token / choose_authorized_users
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_choose_bot_token_prefers_env() -> None:
|
|
env = GatewayEnv(bot_token="env-tok")
|
|
assert choose_bot_token(env, {"bot_token": "store-tok"}) == "env-tok"
|
|
|
|
|
|
def test_choose_bot_token_falls_back_to_store() -> None:
|
|
env = GatewayEnv()
|
|
assert choose_bot_token(env, {"bot_token": "store-tok"}) == "store-tok"
|
|
|
|
|
|
def test_choose_bot_token_missing_raises() -> None:
|
|
env = GatewayEnv()
|
|
with pytest.raises(GatewayConfigurationError, match="bot token is missing"):
|
|
choose_bot_token(env, {})
|
|
|
|
|
|
def test_choose_authorized_users_prefers_store() -> None:
|
|
env = GatewayEnv(allowed_users=["1"])
|
|
policy = MessagingIdentityPolicy(allowed_user_ids=["42"]).model_dump()
|
|
assert choose_authorized_users(env, {"identity_policy": policy}) == ["42"]
|
|
|
|
|
|
def test_choose_authorized_users_falls_back_to_env() -> None:
|
|
env = GatewayEnv(allowed_users=["1", "2"])
|
|
assert choose_authorized_users(env, {}) == ["1", "2"]
|
|
|
|
|
|
def test_choose_authorized_users_empty_warns(
|
|
caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setattr(logging.getLogger("gateway"), "propagate", True)
|
|
env = GatewayEnv()
|
|
with caplog.at_level("WARNING"):
|
|
assert choose_authorized_users(env, {}) == []
|
|
assert "allowed users are not configured" in caplog.text
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# load_gateway_settings (composition root)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_load_gateway_settings_env_and_store(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setenv("TELEGRAM_GATEWAY_MAX_CONCURRENT", "8")
|
|
policy = MessagingIdentityPolicy(allowed_user_ids=["42"]).model_dump()
|
|
record = {"credentials": {"bot_token": "store-tok", "identity_policy": policy}}
|
|
|
|
with patch(_STORE_PATH, return_value=record):
|
|
settings = load_gateway_settings()
|
|
|
|
assert isinstance(settings, GatewaySettings)
|
|
assert settings.bot_token == "store-tok"
|
|
assert settings.allowed_user_ids == ["42"]
|
|
assert settings.max_concurrent_turns == 8
|
|
|
|
|
|
def test_load_gateway_settings_missing_token_raises() -> None:
|
|
with (
|
|
patch(_STORE_PATH, return_value=None),
|
|
pytest.raises(GatewayConfigurationError, match="bot token is missing"),
|
|
):
|
|
load_gateway_settings()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# try_load_gateway_settings_for_startup
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@patch("gateway.config.get_gateway_settings.load_gateway_settings")
|
|
def test_try_load_skips_when_auto_start_disabled(mock_load: MagicMock) -> None:
|
|
mock_load.return_value = GatewaySettings(bot_token="tok", auto_start_enabled=False)
|
|
logger = logging.getLogger("gateway.test")
|
|
assert try_load_gateway_settings_for_startup(logger=logger) is None
|
|
|
|
|
|
@patch("gateway.config.get_gateway_settings.load_gateway_settings")
|
|
def test_try_load_ignores_auto_start_when_disabled(mock_load: MagicMock) -> None:
|
|
mock_load.return_value = GatewaySettings(bot_token="tok", auto_start_enabled=False)
|
|
logger = logging.getLogger("gateway.test")
|
|
settings = try_load_gateway_settings_for_startup(
|
|
logger=logger,
|
|
respect_auto_start=False,
|
|
)
|
|
assert settings is not None
|
|
assert settings.bot_token == "tok"
|
|
|
|
|
|
@patch("gateway.config.get_gateway_settings.load_gateway_settings")
|
|
def test_try_load_skips_on_configuration_error(mock_load: MagicMock) -> None:
|
|
mock_load.side_effect = GatewayConfigurationError("missing integration")
|
|
logger = logging.getLogger("gateway.test")
|
|
assert try_load_gateway_settings_for_startup(logger=logger) is None
|
|
|
|
|
|
@patch("gateway.config.get_gateway_settings.load_gateway_settings")
|
|
def test_try_load_skips_when_bot_token_empty(mock_load: MagicMock) -> None:
|
|
mock_load.return_value = GatewaySettings(bot_token="")
|
|
logger = logging.getLogger("gateway.test")
|
|
assert (
|
|
try_load_gateway_settings_for_startup(
|
|
logger=logger,
|
|
respect_auto_start=False,
|
|
)
|
|
is None
|
|
)
|