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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user