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

214 lines
6.8 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import pytest
from takopi import cli
from takopi.backends import EngineBackend, SetupIssue
from takopi.settings import TakopiSettings
from takopi.transports import SetupResult
@dataclass
class _DummyLock:
released: bool = False
def release(self) -> None:
self.released = True
class _FakeTransport:
id = "fake"
description = "fake transport"
def __init__(self, setup: SetupResult) -> None:
self._setup = setup
self.check_calls: list[tuple[object, str | None]] = []
self.lock_calls: list[tuple[object, Path]] = []
self.build_calls: list[dict[str, object]] = []
def check_setup(self, engine_backend, *, transport_override=None) -> SetupResult:
self.check_calls.append((engine_backend, transport_override))
return self._setup
def interactive_setup(self, *, force: bool) -> bool:
_ = force
return True
def lock_token(self, *, transport_config: object, _config_path: Path) -> str | None:
self.lock_calls.append((transport_config, _config_path))
return "lock"
def build_and_run(
self,
*,
transport_config: object,
config_path: Path,
runtime,
final_notify: bool,
default_engine_override: str | None,
) -> None:
self.build_calls.append(
{
"transport_config": transport_config,
"config_path": config_path,
"runtime": runtime,
"final_notify": final_notify,
"default_engine_override": default_engine_override,
}
)
def _engine_backend() -> EngineBackend:
return EngineBackend(id="codex", build_runner=lambda _cfg, _path: None)
def _settings() -> TakopiSettings:
return TakopiSettings.model_validate(
{
"transport": "telegram",
"transports": {"telegram": {"bot_token": "token", "chat_id": 123}},
}
)
def test_run_auto_router_success_releases_lock(monkeypatch, tmp_path: Path) -> None:
setup = SetupResult(issues=[], config_path=tmp_path / "takopi.toml")
transport = _FakeTransport(setup)
engine_backend = _engine_backend()
config_path = tmp_path / "takopi.toml"
monkeypatch.setattr(
cli,
"_resolve_setup_engine",
lambda _override: (None, None, None, "codex", engine_backend),
)
monkeypatch.setattr(cli, "_resolve_transport_id", lambda _override: "wire")
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)
spec_calls: dict[str, object] = {}
class _Spec:
def to_runtime(self, *, config_path: Path):
spec_calls["runtime_config_path"] = config_path
return "runtime"
def _build_runtime_spec(**kwargs):
spec_calls.update(kwargs)
return _Spec()
monkeypatch.setattr(cli, "build_runtime_spec", _build_runtime_spec)
lock = _DummyLock()
monkeypatch.setattr(cli, "acquire_config_lock", lambda _path, _token: lock)
cli._run_auto_router(
default_engine_override=None,
transport_override="wire",
final_notify=True,
debug=False,
onboard=False,
)
assert transport.build_calls
assert lock.released is True
assert spec_calls["reserved"] == cli.RESERVED_CHAT_COMMANDS
assert transport.lock_calls[0][0] == {}
def test_run_auto_router_requires_tty_for_onboard(monkeypatch, tmp_path: Path) -> None:
setup = SetupResult(issues=[], config_path=tmp_path / "takopi.toml")
transport = _FakeTransport(setup)
monkeypatch.setattr(
cli,
"_resolve_setup_engine",
lambda _override: (None, None, None, "codex", _engine_backend()),
)
monkeypatch.setattr(cli, "_resolve_transport_id", lambda _override: "fake")
monkeypatch.setattr(cli, "get_transport", lambda _id, allowlist=None: transport)
monkeypatch.setattr(cli, "_should_run_interactive", lambda: False)
monkeypatch.setattr(cli, "setup_logging", lambda **_kwargs: None)
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=True,
)
assert exc.value.exit_code == 1
assert not transport.build_calls
def test_run_auto_router_missing_config_noninteractive(
monkeypatch, tmp_path: Path
) -> None:
setup = SetupResult(
issues=[SetupIssue(title="create a config", lines=())],
config_path=tmp_path / "missing.toml",
)
transport = _FakeTransport(setup)
monkeypatch.setattr(
cli,
"_resolve_setup_engine",
lambda _override: (None, None, None, "codex", _engine_backend()),
)
monkeypatch.setattr(cli, "_resolve_transport_id", lambda _override: "fake")
monkeypatch.setattr(cli, "get_transport", lambda _id, allowlist=None: transport)
monkeypatch.setattr(cli, "_should_run_interactive", lambda: False)
monkeypatch.setattr(cli, "setup_logging", lambda **_kwargs: None)
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.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