241 lines
7.6 KiB
Python
241 lines
7.6 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import tomllib
|
|
|
|
from typer.testing import CliRunner
|
|
|
|
from takopi import cli
|
|
from takopi.config import ConfigError
|
|
from takopi.plugins import (
|
|
COMMAND_GROUP,
|
|
ENGINE_GROUP,
|
|
TRANSPORT_GROUP,
|
|
PluginLoadError,
|
|
)
|
|
from takopi.settings import TakopiSettings
|
|
from tests.plugin_fixtures import FakeEntryPoint
|
|
|
|
|
|
def _min_config() -> dict:
|
|
return {
|
|
"transport": "telegram",
|
|
"transports": {"telegram": {"bot_token": "token", "chat_id": 123}},
|
|
}
|
|
|
|
|
|
def test_init_registers_project(monkeypatch, tmp_path: Path) -> None:
|
|
config = _min_config()
|
|
config_path = tmp_path / "takopi.toml"
|
|
repo_path = tmp_path / "repo"
|
|
repo_path.mkdir()
|
|
monkeypatch.chdir(repo_path)
|
|
|
|
monkeypatch.setattr(cli, "load_or_init_config", lambda: (config, config_path))
|
|
monkeypatch.setattr(cli, "resolve_main_worktree_root", lambda _path: None)
|
|
monkeypatch.setattr(cli, "resolve_default_base", lambda _path: "main")
|
|
monkeypatch.setattr(cli, "list_backend_ids", lambda allowlist=None: ["codex"])
|
|
monkeypatch.setattr(cli, "resolve_plugins_allowlist", lambda _settings: None)
|
|
monkeypatch.setattr(cli.typer, "prompt", lambda *args, **kwargs: "demo")
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli.create_app(), ["init", "--default"])
|
|
|
|
assert result.exit_code == 0
|
|
assert "saved project" in result.output
|
|
|
|
data = tomllib.loads(config_path.read_text(encoding="utf-8"))
|
|
project = data["projects"]["demo"]
|
|
assert project["path"] == str(repo_path)
|
|
assert project["default_engine"] == "codex"
|
|
assert project["worktrees_dir"] == ".worktrees"
|
|
assert project["worktree_base"] == "main"
|
|
assert data["default_project"] == "demo"
|
|
|
|
|
|
def test_init_declines_overwrite(monkeypatch, tmp_path: Path) -> None:
|
|
config = _min_config()
|
|
config["projects"] = {"demo": {"path": "/tmp/repo"}}
|
|
config_path = tmp_path / "takopi.toml"
|
|
repo_path = tmp_path / "repo"
|
|
repo_path.mkdir()
|
|
monkeypatch.chdir(repo_path)
|
|
|
|
monkeypatch.setattr(cli, "load_or_init_config", lambda: (config, config_path))
|
|
monkeypatch.setattr(cli, "resolve_main_worktree_root", lambda _path: None)
|
|
monkeypatch.setattr(cli, "resolve_default_base", lambda _path: None)
|
|
monkeypatch.setattr(cli, "list_backend_ids", lambda allowlist=None: ["codex"])
|
|
monkeypatch.setattr(cli, "resolve_plugins_allowlist", lambda _settings: None)
|
|
monkeypatch.setattr(cli.typer, "confirm", lambda *args, **kwargs: False)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli.create_app(), ["init", "demo"])
|
|
|
|
assert result.exit_code == 1
|
|
|
|
|
|
def test_plugins_cmd_loads_and_reports_errors(monkeypatch) -> None:
|
|
entrypoints = {
|
|
ENGINE_GROUP: [
|
|
FakeEntryPoint(
|
|
"codex",
|
|
"takopi.runners.codex:BACKEND",
|
|
ENGINE_GROUP,
|
|
dist_name="takopi",
|
|
),
|
|
FakeEntryPoint(
|
|
"broken",
|
|
"takopi.runners.broken:BACKEND",
|
|
ENGINE_GROUP,
|
|
dist_name="takopi",
|
|
),
|
|
],
|
|
TRANSPORT_GROUP: [
|
|
FakeEntryPoint(
|
|
"telegram",
|
|
"takopi.transports.telegram:BACKEND",
|
|
TRANSPORT_GROUP,
|
|
dist_name="takopi",
|
|
)
|
|
],
|
|
COMMAND_GROUP: [
|
|
FakeEntryPoint(
|
|
"hello",
|
|
"takopi.commands.hello:BACKEND",
|
|
COMMAND_GROUP,
|
|
dist_name="thirdparty",
|
|
)
|
|
],
|
|
}
|
|
|
|
def _list_entrypoints(group: str, reserved_ids=None):
|
|
_ = reserved_ids
|
|
return entrypoints[group]
|
|
|
|
calls: list[tuple[str, str]] = []
|
|
|
|
def _get_backend(name: str, allowlist=None):
|
|
_ = allowlist
|
|
calls.append(("engine", name))
|
|
if name == "broken":
|
|
raise ConfigError("boom")
|
|
return object()
|
|
|
|
def _get_transport(name: str, allowlist=None):
|
|
_ = allowlist
|
|
calls.append(("transport", name))
|
|
return object()
|
|
|
|
def _get_command(name: str, allowlist=None):
|
|
_ = allowlist
|
|
calls.append(("command", name))
|
|
return object()
|
|
|
|
monkeypatch.setattr(cli, "_load_settings_optional", lambda: (None, None))
|
|
monkeypatch.setattr(cli, "resolve_plugins_allowlist", lambda _settings: ["takopi"])
|
|
monkeypatch.setattr(cli, "list_entrypoints", _list_entrypoints)
|
|
monkeypatch.setattr(cli, "get_backend", _get_backend)
|
|
monkeypatch.setattr(cli, "get_transport", _get_transport)
|
|
monkeypatch.setattr(cli, "get_command", _get_command)
|
|
monkeypatch.setattr(
|
|
cli,
|
|
"get_load_errors",
|
|
lambda: [
|
|
PluginLoadError(
|
|
ENGINE_GROUP,
|
|
"broken",
|
|
"takopi.runners.broken:BACKEND",
|
|
"takopi",
|
|
"boom",
|
|
),
|
|
PluginLoadError(
|
|
TRANSPORT_GROUP,
|
|
"wire",
|
|
"takopi.transports.wire:BACKEND",
|
|
"takopi",
|
|
"missing",
|
|
),
|
|
PluginLoadError(
|
|
COMMAND_GROUP,
|
|
"hello",
|
|
"takopi.commands.hello:BACKEND",
|
|
"thirdparty",
|
|
"oops",
|
|
),
|
|
],
|
|
)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli.create_app(), ["plugins", "--load"])
|
|
|
|
assert result.exit_code == 0
|
|
assert "engine backends:" in result.output
|
|
assert "transport backends:" in result.output
|
|
assert "command backends:" in result.output
|
|
assert "codex (takopi) enabled" in result.output
|
|
assert "hello (thirdparty) disabled" in result.output
|
|
assert "errors:" in result.output
|
|
assert "engine broken (takopi): boom" in result.output
|
|
assert "transport wire (takopi): missing" in result.output
|
|
assert "command hello (thirdparty): oops" in result.output
|
|
|
|
assert ("engine", "codex") in calls
|
|
assert ("engine", "broken") in calls
|
|
assert ("transport", "telegram") in calls
|
|
assert ("command", "hello") not in calls
|
|
|
|
|
|
def test_onboarding_paths_calls_debug(monkeypatch) -> None:
|
|
called = {"count": 0}
|
|
|
|
def _debug() -> None:
|
|
called["count"] += 1
|
|
|
|
monkeypatch.setattr(cli.onboarding, "debug_onboarding_paths", _debug)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli.create_app(), ["onboarding-paths"])
|
|
|
|
assert result.exit_code == 0
|
|
assert called["count"] == 1
|
|
|
|
|
|
def test_config_path_cmd_outputs_override(tmp_path: Path) -> None:
|
|
config_path = tmp_path / "takopi.toml"
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli.create_app(),
|
|
["config", "path", "--config-path", str(config_path)],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert result.output.strip() == str(config_path)
|
|
|
|
|
|
def test_config_path_cmd_defaults_to_home(monkeypatch, tmp_path: Path) -> None:
|
|
monkeypatch.setenv("HOME", str(tmp_path))
|
|
config_path = tmp_path / ".takopi" / "takopi.toml"
|
|
monkeypatch.setattr(cli, "HOME_CONFIG_PATH", config_path)
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli.create_app(), ["config", "path"])
|
|
|
|
assert result.exit_code == 0
|
|
assert result.output.strip() == "~/.takopi/takopi.toml"
|
|
|
|
|
|
def test_doctor_rejects_non_telegram_transport(monkeypatch) -> None:
|
|
settings = TakopiSettings.model_validate(
|
|
{
|
|
"transport": "local",
|
|
"transports": {"telegram": {"bot_token": "token", "chat_id": 123}},
|
|
}
|
|
)
|
|
monkeypatch.setattr(cli, "load_settings", lambda: (settings, Path("x")))
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli.create_app(), ["doctor"])
|
|
|
|
assert result.exit_code == 1
|
|
assert "telegram transport only" in result.output
|