fix: make telegram config optional for external transports (#177)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: banteg <4562643+banteg@users.noreply.github.com>
This commit is contained in:
ayvee
2026-03-02 15:09:29 +07:00
committed by GitHub
parent 6cf469c8ac
commit 058092c1a1
11 changed files with 207 additions and 31 deletions
+35
View File
@@ -176,3 +176,38 @@ def test_run_auto_router_missing_config_noninteractive(
assert exc.value.exit_code == 1
assert not transport.build_calls
def test_run_auto_router_rejects_missing_telegram_config(
monkeypatch, tmp_path: Path
) -> None:
setup = SetupResult(issues=[], config_path=tmp_path / "takopi.toml")
transport = _FakeTransport(setup)
config_path = tmp_path / "takopi.toml"
settings = TakopiSettings.model_validate(
{"transport": "telegram", "transports": {}}
)
monkeypatch.setattr(
cli,
"_resolve_setup_engine",
lambda _override: (None, None, None, "codex", _engine_backend()),
)
monkeypatch.setattr(cli, "_resolve_transport_id", lambda _override: "telegram")
monkeypatch.setattr(cli, "get_transport", lambda _id, allowlist=None: transport)
monkeypatch.setattr(cli, "load_settings", lambda: (settings, config_path))
monkeypatch.setattr(cli, "setup_logging", lambda **_kwargs: None)
monkeypatch.setattr(cli, "build_runtime_spec", lambda **_kwargs: object())
with pytest.raises(cli.typer.Exit) as exc:
cli._run_auto_router(
default_engine_override=None,
transport_override=None,
final_notify=True,
debug=False,
onboard=False,
)
assert exc.value.exit_code == 1
assert not transport.lock_calls
assert not transport.build_calls
+26
View File
@@ -68,3 +68,29 @@ def test_chat_id_command_uses_config_token(monkeypatch) -> None:
assert result.exit_code == 0
assert "chat_id = 321" in result.output
def test_chat_id_command_without_telegram_config(monkeypatch) -> None:
settings = TakopiSettings.model_validate(
{"transport": "my-transport", "transports": {}}
)
monkeypatch.setattr(cli, "_load_settings_optional", lambda: (settings, Path("x")))
async def _capture(*, token: str | None = None):
assert token is None
return onboarding.ChatInfo(
chat_id=321,
username=None,
title="takopi",
first_name=None,
last_name=None,
chat_type="supergroup",
)
monkeypatch.setattr(cli.onboarding, "capture_chat_id", _capture)
runner = CliRunner()
result = runner.invoke(cli.create_app(), ["chat-id"])
assert result.exit_code == 0
assert "chat_id = 321" in result.output
+13
View File
@@ -56,6 +56,19 @@ def test_doctor_errors_exit_nonzero(monkeypatch) -> None:
assert "telegram token: error" in result.output
def test_doctor_missing_telegram_config_exits(monkeypatch) -> None:
settings = TakopiSettings.model_validate(
{"transport": "telegram", "transports": {}}
)
monkeypatch.setattr(cli, "load_settings", lambda: (settings, Path("x")))
runner = CliRunner()
result = runner.invoke(cli.create_app(), ["doctor"])
assert result.exit_code == 1
assert "Missing [transports.telegram]" in result.output
class _FakeBot:
def __init__(self, me: User | None, chat: Chat | None) -> None:
self._me = me
+13 -7
View File
@@ -5,7 +5,7 @@ import pytest
from takopi import cli
from takopi.config import ConfigError
from takopi.lockfile import LockError
from takopi.settings import TakopiSettings
from takopi.settings import TakopiSettings, TelegramTransportSettings
def _settings(overrides: dict | None = None) -> TakopiSettings:
@@ -18,6 +18,12 @@ def _settings(overrides: dict | None = None) -> TakopiSettings:
return TakopiSettings.model_validate(payload)
def _telegram_settings(settings: TakopiSettings) -> TelegramTransportSettings:
tg = settings.transports.telegram
assert tg is not None
return tg
def test_parse_key_path_valid() -> None:
assert cli._parse_key_path("transports.telegram.chat_id") == [
"transports",
@@ -98,7 +104,7 @@ def test_resolve_transport_id_override(monkeypatch) -> None:
def test_doctor_file_checks() -> None:
settings = _settings()
checks = cli._doctor_file_checks(settings)
checks = cli._doctor_file_checks(_telegram_settings(settings))
assert checks[0].detail == "disabled"
settings = _settings(
@@ -112,13 +118,13 @@ def test_doctor_file_checks() -> None:
}
}
)
checks = cli._doctor_file_checks(settings)
checks = cli._doctor_file_checks(_telegram_settings(settings))
assert checks[0].status == "warning"
def test_doctor_voice_checks(monkeypatch) -> None:
settings = _settings()
checks = cli._doctor_voice_checks(settings)
checks = cli._doctor_voice_checks(_telegram_settings(settings))
assert checks[0].detail == "disabled"
settings = _settings(
@@ -133,7 +139,7 @@ def test_doctor_voice_checks(monkeypatch) -> None:
}
)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
checks = cli._doctor_voice_checks(settings)
checks = cli._doctor_voice_checks(_telegram_settings(settings))
assert checks[0].status == "error"
assert checks[0].detail == "API key not set"
@@ -149,12 +155,12 @@ def test_doctor_voice_checks(monkeypatch) -> None:
}
}
)
checks = cli._doctor_voice_checks(settings_with_key)
checks = cli._doctor_voice_checks(_telegram_settings(settings_with_key))
assert checks[0].status == "ok"
assert checks[0].detail == "voice_transcription_api_key set"
monkeypatch.setenv("OPENAI_API_KEY", "key")
checks = cli._doctor_voice_checks(settings)
checks = cli._doctor_voice_checks(_telegram_settings(settings))
assert checks[0].status == "ok"
+41
View File
@@ -75,3 +75,44 @@ def test_check_setup_marks_invalid_bot_token(monkeypatch, tmp_path: Path) -> Non
titles = {issue.title for issue in result.issues}
assert "configure telegram" in titles
def test_check_setup_skips_telegram_validation_for_external_transport(
monkeypatch, tmp_path: Path
) -> None:
backend = engines.get_backend("codex")
monkeypatch.setattr(onboarding.shutil, "which", lambda _name: "/usr/bin/codex")
monkeypatch.setattr(
onboarding,
"load_settings",
lambda: (
TakopiSettings.model_validate(
{"transport": "my-transport", "transports": {}}
),
tmp_path / "takopi.toml",
),
)
result = onboarding.check_setup(backend, transport_override="my-transport")
assert result.ok is True
assert len(result.issues) == 0
def test_check_setup_external_transport_missing_config(
monkeypatch, tmp_path: Path
) -> None:
backend = engines.get_backend("codex")
monkeypatch.setattr(onboarding.shutil, "which", lambda _name: "/usr/bin/codex")
monkeypatch.setattr(onboarding, "HOME_CONFIG_PATH", tmp_path / "takopi.toml")
def _raise() -> None:
raise onboarding.ConfigError("Missing config file")
monkeypatch.setattr(onboarding, "load_settings", _raise)
result = onboarding.check_setup(backend, transport_override="my-transport")
titles = {issue.title for issue in result.issues}
assert "create a config" in titles
assert "configure telegram" not in titles
+35
View File
@@ -176,6 +176,15 @@ def test_transport_config_telegram_and_extra(tmp_path: Path) -> None:
settings.transport_config("discord", config_path=config_path)
def test_transport_config_telegram_missing(tmp_path: Path) -> None:
config_path = tmp_path / "takopi.toml"
settings = TakopiSettings.model_validate(
{"transport": "discord", "transports": {"discord": {"token": "abc"}}}
)
with pytest.raises(ConfigError, match=r"Missing \[transports\.telegram\]"):
settings.transport_config("telegram", config_path=config_path)
def test_bot_token_none_rejected(tmp_path: Path) -> None:
config_path = tmp_path / "takopi.toml"
data = {
@@ -198,6 +207,15 @@ def test_require_telegram_rejects_non_telegram_transport(tmp_path: Path) -> None
require_telegram(settings, config_path)
def test_require_telegram_rejects_missing_telegram_config(tmp_path: Path) -> None:
config_path = tmp_path / "takopi.toml"
settings = TakopiSettings.model_validate(
{"transport": "telegram", "transports": {}}
)
with pytest.raises(ConfigError, match=r"Missing \[transports\.telegram\]"):
require_telegram(settings, config_path)
def test_load_settings_if_exists_missing(tmp_path: Path) -> None:
config_path = tmp_path / "missing.toml"
assert load_settings_if_exists(config_path) is None
@@ -235,3 +253,20 @@ def test_load_settings_rejects_non_file(tmp_path: Path) -> None:
config_path.mkdir()
with pytest.raises(ConfigError, match="exists but is not a file"):
load_settings(config_path)
def test_load_settings_without_telegram(tmp_path: Path) -> None:
config_path = tmp_path / "takopi.toml"
config_path.write_text(
'transport = "my-transport"\n\n[transports.my-transport]\nsome_key = "value"\n',
encoding="utf-8",
)
settings, loaded_path = load_settings(config_path)
assert loaded_path == config_path
assert settings.transport == "my-transport"
assert settings.transports.telegram is None
assert settings.transport_config("my-transport", config_path=config_path) == {
"some_key": "value"
}