Files
takopi/tests/test_cli_helpers.py
2026-03-02 12:09:29 +04:00

201 lines
6.0 KiB
Python

from __future__ import annotations
import pytest
from takopi import cli
from takopi.config import ConfigError
from takopi.lockfile import LockError
from takopi.settings import TakopiSettings, TelegramTransportSettings
def _settings(overrides: dict | None = None) -> TakopiSettings:
payload = {
"transport": "telegram",
"transports": {"telegram": {"bot_token": "token", "chat_id": 123}},
}
if overrides:
payload.update(overrides)
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",
"telegram",
"chat_id",
]
def test_parse_key_path_invalid_segment() -> None:
with pytest.raises(ConfigError):
cli._parse_key_path("transports..chat_id")
def test_parse_value_toml_and_fallback() -> None:
assert cli._parse_value("true") is True
assert cli._parse_value("123") == 123
assert cli._parse_value("not-toml") == "not-toml"
def test_toml_literal_and_error() -> None:
assert cli._toml_literal("hello") == '"hello"'
with pytest.raises(ConfigError):
cli._toml_literal({"a": 1})
def test_flatten_config() -> None:
flattened = cli._flatten_config(
{"transports": {"telegram": {"chat_id": 123}}, "watch_config": True}
)
assert ("transports.telegram.chat_id", 123) in flattened
assert ("watch_config", True) in flattened
def test_normalized_value_from_settings() -> None:
settings = _settings()
assert cli._normalized_value_from_settings(settings, ["transport"]) == "telegram"
assert (
cli._normalized_value_from_settings(
settings, ["transports", "telegram", "chat_id"]
)
== 123
)
def test_should_run_interactive(monkeypatch) -> None:
class _Tty:
def isatty(self) -> bool:
return True
class _NotTty:
def isatty(self) -> bool:
return False
monkeypatch.setenv("TAKOPI_NO_INTERACTIVE", "1")
assert cli._should_run_interactive() is False
monkeypatch.delenv("TAKOPI_NO_INTERACTIVE")
monkeypatch.setattr(cli.sys, "stdin", _Tty())
monkeypatch.setattr(cli.sys, "stdout", _Tty())
assert cli._should_run_interactive() is True
monkeypatch.setattr(cli.sys, "stdin", _NotTty())
monkeypatch.setattr(cli.sys, "stdout", _Tty())
assert cli._should_run_interactive() is False
def test_resolve_transport_id_override(monkeypatch) -> None:
assert cli._resolve_transport_id(" telegram ") == "telegram"
with pytest.raises(ConfigError):
cli._resolve_transport_id(" ")
def _raise() -> None:
raise ConfigError("boom")
monkeypatch.setattr(cli, "load_or_init_config", _raise)
assert cli._resolve_transport_id(None) == "telegram"
def test_doctor_file_checks() -> None:
settings = _settings()
checks = cli._doctor_file_checks(_telegram_settings(settings))
assert checks[0].detail == "disabled"
settings = _settings(
{
"transports": {
"telegram": {
"bot_token": "token",
"chat_id": 1,
"files": {"enabled": True},
}
}
}
)
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(_telegram_settings(settings))
assert checks[0].detail == "disabled"
settings = _settings(
{
"transports": {
"telegram": {
"bot_token": "token",
"chat_id": 1,
"voice_transcription": True,
}
}
}
)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
checks = cli._doctor_voice_checks(_telegram_settings(settings))
assert checks[0].status == "error"
assert checks[0].detail == "API key not set"
settings_with_key = _settings(
{
"transports": {
"telegram": {
"bot_token": "token",
"chat_id": 1,
"voice_transcription": True,
"voice_transcription_api_key": "local",
}
}
}
)
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(_telegram_settings(settings))
assert checks[0].status == "ok"
def test_load_settings_optional(monkeypatch, tmp_path) -> None:
def _raise() -> None:
raise ConfigError("boom")
monkeypatch.setattr(cli, "load_settings_if_exists", _raise)
assert cli._load_settings_optional() == (None, None)
monkeypatch.setattr(cli, "load_settings_if_exists", lambda: None)
assert cli._load_settings_optional() == (None, None)
settings = _settings()
config_path = tmp_path / "takopi.toml"
monkeypatch.setattr(cli, "load_settings_if_exists", lambda: (settings, config_path))
assert cli._load_settings_optional() == (settings, config_path)
def test_acquire_config_lock_reports_error(monkeypatch, tmp_path) -> None:
config_path = tmp_path / "takopi.toml"
error = LockError(path=config_path, state="running")
def _raise(*_args, **_kwargs):
raise error
messages: list[tuple[str, bool]] = []
monkeypatch.setattr(cli, "acquire_lock", _raise)
monkeypatch.setattr(
cli.typer, "echo", lambda msg, err=False: messages.append((msg, err))
)
with pytest.raises(cli.typer.Exit) as exc:
cli.acquire_config_lock(config_path, "token")
assert exc.value.exit_code == 1
assert any("already running" in msg for msg, _ in messages)