feat: onboarding overhaul, persona-based setup (#132)
This commit is contained in:
@@ -16,7 +16,7 @@ def test_chat_id_command_updates_project_chat_id(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr("takopi.config.HOME_CONFIG_PATH", config_path)
|
||||
monkeypatch.setattr(cli, "_load_settings_optional", lambda: (None, None))
|
||||
|
||||
def _capture(*, token: str | None = None):
|
||||
async def _capture(*, token: str | None = None):
|
||||
assert token == "token"
|
||||
return onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
@@ -50,7 +50,7 @@ def test_chat_id_command_uses_config_token(monkeypatch) -> None:
|
||||
)
|
||||
monkeypatch.setattr(cli, "_load_settings_optional", lambda: (settings, Path("x")))
|
||||
|
||||
def _capture(*, token: str | None = None):
|
||||
async def _capture(*, token: str | None = None):
|
||||
assert token == "config-token"
|
||||
return onboarding.ChatInfo(
|
||||
chat_id=321,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
from pathlib import Path
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from takopi import cli
|
||||
from takopi.settings import TakopiSettings
|
||||
|
||||
|
||||
def _settings() -> TakopiSettings:
|
||||
return TakopiSettings.model_validate(
|
||||
{
|
||||
"transport": "telegram",
|
||||
"transports": {"telegram": {"bot_token": "token", "chat_id": 123}},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_doctor_ok(monkeypatch) -> None:
|
||||
settings = _settings()
|
||||
monkeypatch.setattr(cli, "load_settings", lambda: (settings, Path("x")))
|
||||
monkeypatch.setattr(cli, "resolve_plugins_allowlist", lambda _settings: None)
|
||||
monkeypatch.setattr(cli, "list_backend_ids", lambda allowlist=None: ["codex"])
|
||||
|
||||
async def _fake_checks(*_args, **_kwargs):
|
||||
return [cli.DoctorCheck("telegram token", "ok", "@bot")]
|
||||
|
||||
monkeypatch.setattr(cli, "_doctor_telegram_checks", _fake_checks)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli.create_app(), ["doctor"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "takopi doctor" in result.output
|
||||
assert "telegram token: ok" in result.output
|
||||
|
||||
|
||||
def test_doctor_errors_exit_nonzero(monkeypatch) -> None:
|
||||
settings = _settings()
|
||||
monkeypatch.setattr(cli, "load_settings", lambda: (settings, Path("x")))
|
||||
monkeypatch.setattr(cli, "resolve_plugins_allowlist", lambda _settings: None)
|
||||
monkeypatch.setattr(cli, "list_backend_ids", lambda allowlist=None: ["codex"])
|
||||
|
||||
async def _fake_checks(*_args, **_kwargs):
|
||||
return [cli.DoctorCheck("telegram token", "error", "bad token")]
|
||||
|
||||
monkeypatch.setattr(cli, "_doctor_telegram_checks", _fake_checks)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli.create_app(), ["doctor"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "telegram token: error" in result.output
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import anyio
|
||||
from functools import partial
|
||||
|
||||
from takopi.backends import EngineBackend
|
||||
from takopi.config import dump_toml
|
||||
from takopi.telegram import onboarding
|
||||
@@ -39,32 +42,56 @@ def test_render_config_escapes() -> None:
|
||||
assert config.endswith("\n")
|
||||
|
||||
|
||||
class _FakeQuestion:
|
||||
class FakeQuestion:
|
||||
def __init__(self, value):
|
||||
self._value = value
|
||||
|
||||
def ask(self):
|
||||
return self._value
|
||||
|
||||
async def ask_async(self):
|
||||
return self._value
|
||||
|
||||
def _queue(values):
|
||||
|
||||
def queue_answers(values):
|
||||
it = iter(values)
|
||||
|
||||
def _make(*_args, **_kwargs):
|
||||
return _FakeQuestion(next(it))
|
||||
return FakeQuestion(next(it))
|
||||
|
||||
return _make
|
||||
|
||||
|
||||
def _queue_values(values):
|
||||
def queue_values(values):
|
||||
it = iter(values)
|
||||
|
||||
def _next(*_args, **_kwargs):
|
||||
async def _next(*_args, **_kwargs):
|
||||
return next(it)
|
||||
|
||||
return _next
|
||||
|
||||
|
||||
def patch_live_services(
|
||||
monkeypatch,
|
||||
*,
|
||||
bot: User,
|
||||
chat: onboarding.ChatInfo,
|
||||
topics_issue=None,
|
||||
) -> None:
|
||||
async def _get_bot_info(self, _token: str):
|
||||
return bot
|
||||
|
||||
async def _wait_for_chat(self, _token: str):
|
||||
return chat
|
||||
|
||||
async def _validate_topics(self, _token: str, _chat_id: int, _scope):
|
||||
return topics_issue
|
||||
|
||||
monkeypatch.setattr(onboarding.LiveServices, "get_bot_info", _get_bot_info)
|
||||
monkeypatch.setattr(onboarding.LiveServices, "wait_for_chat", _wait_for_chat)
|
||||
monkeypatch.setattr(onboarding.LiveServices, "validate_topics", _validate_topics)
|
||||
|
||||
|
||||
def test_interactive_setup_skips_when_config_exists(monkeypatch, tmp_path) -> None:
|
||||
config_path = tmp_path / "takopi.toml"
|
||||
config_path.write_text(
|
||||
@@ -73,7 +100,7 @@ def test_interactive_setup_skips_when_config_exists(monkeypatch, tmp_path) -> No
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setattr(onboarding, "HOME_CONFIG_PATH", config_path)
|
||||
assert onboarding.interactive_setup(force=False) is True
|
||||
assert anyio.run(partial(onboarding.interactive_setup, force=False)) is True
|
||||
|
||||
|
||||
def test_interactive_setup_writes_config(monkeypatch, tmp_path) -> None:
|
||||
@@ -84,36 +111,38 @@ def test_interactive_setup_writes_config(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(onboarding, "list_backends", lambda: [backend])
|
||||
monkeypatch.setattr(onboarding.shutil, "which", lambda _cmd: "/usr/bin/codex")
|
||||
|
||||
monkeypatch.setattr(onboarding, "_confirm", _queue_values([True, True]))
|
||||
monkeypatch.setattr(onboarding, "confirm_prompt", queue_values([True, True]))
|
||||
monkeypatch.setattr(
|
||||
onboarding.questionary, "password", _queue(["123456789:ABCdef"])
|
||||
onboarding.questionary, "password", queue_answers(["123456789:ABCdef"])
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
onboarding.questionary,
|
||||
"select",
|
||||
queue_answers(["assistant", "codex"]),
|
||||
)
|
||||
patch_live_services(
|
||||
monkeypatch,
|
||||
bot=User(id=1, username="my_bot"),
|
||||
chat=onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(onboarding.questionary, "select", _queue(["codex"]))
|
||||
|
||||
def _fake_run(func, *args, **kwargs):
|
||||
if func is onboarding.get_bot_info:
|
||||
return User(id=1, username="my_bot")
|
||||
if func is onboarding.wait_for_chat:
|
||||
return onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
)
|
||||
if func is onboarding._send_confirmation:
|
||||
return True
|
||||
raise AssertionError(f"unexpected anyio.run target: {func}")
|
||||
|
||||
monkeypatch.setattr(onboarding.anyio, "run", _fake_run)
|
||||
|
||||
assert onboarding.interactive_setup(force=False) is True
|
||||
assert anyio.run(partial(onboarding.interactive_setup, force=False)) is True
|
||||
saved = config_path.read_text(encoding="utf-8")
|
||||
assert 'transport = "telegram"' in saved
|
||||
assert "[transports.telegram]" in saved
|
||||
assert 'bot_token = "123456789:ABCdef"' in saved
|
||||
assert "chat_id = 123" in saved
|
||||
assert 'session_mode = "chat"' in saved
|
||||
assert "show_resume_line = false" in saved
|
||||
assert "[transports.telegram.topics]" in saved
|
||||
assert "enabled = false" in saved
|
||||
assert 'default_engine = "codex"' in saved
|
||||
|
||||
|
||||
@@ -129,31 +158,29 @@ def test_interactive_setup_preserves_projects(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(onboarding, "list_backends", lambda: [backend])
|
||||
monkeypatch.setattr(onboarding.shutil, "which", lambda _cmd: "/usr/bin/codex")
|
||||
|
||||
monkeypatch.setattr(onboarding, "_confirm", _queue_values([True, True, True]))
|
||||
monkeypatch.setattr(onboarding, "confirm_prompt", queue_values([True, True, True]))
|
||||
monkeypatch.setattr(
|
||||
onboarding.questionary, "password", _queue(["123456789:ABCdef"])
|
||||
onboarding.questionary, "password", queue_answers(["123456789:ABCdef"])
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
onboarding.questionary,
|
||||
"select",
|
||||
queue_answers(["assistant", "codex"]),
|
||||
)
|
||||
patch_live_services(
|
||||
monkeypatch,
|
||||
bot=User(id=1, username="my_bot"),
|
||||
chat=onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(onboarding.questionary, "select", _queue(["codex"]))
|
||||
|
||||
def _fake_run(func, *args, **kwargs):
|
||||
if func is onboarding.get_bot_info:
|
||||
return User(id=1, username="my_bot")
|
||||
if func is onboarding.wait_for_chat:
|
||||
return onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
)
|
||||
if func is onboarding._send_confirmation:
|
||||
return True
|
||||
raise AssertionError(f"unexpected anyio.run target: {func}")
|
||||
|
||||
monkeypatch.setattr(onboarding.anyio, "run", _fake_run)
|
||||
|
||||
assert onboarding.interactive_setup(force=True) is True
|
||||
assert anyio.run(partial(onboarding.interactive_setup, force=True)) is True
|
||||
saved = config_path.read_text(encoding="utf-8")
|
||||
assert "[projects.z80]" in saved
|
||||
assert 'path = "/tmp/repo"' in saved
|
||||
@@ -167,30 +194,29 @@ def test_interactive_setup_no_agents_aborts(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(onboarding, "list_backends", lambda: [backend])
|
||||
monkeypatch.setattr(onboarding.shutil, "which", lambda _cmd: None)
|
||||
|
||||
monkeypatch.setattr(onboarding, "_confirm", _queue_values([True, False]))
|
||||
monkeypatch.setattr(onboarding, "confirm_prompt", queue_values([True, False]))
|
||||
monkeypatch.setattr(
|
||||
onboarding.questionary, "password", _queue(["123456789:ABCdef"])
|
||||
onboarding.questionary, "password", queue_answers(["123456789:ABCdef"])
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
onboarding.questionary,
|
||||
"select",
|
||||
queue_answers(["assistant"]),
|
||||
)
|
||||
patch_live_services(
|
||||
monkeypatch,
|
||||
bot=User(id=1, username="my_bot"),
|
||||
chat=onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
),
|
||||
)
|
||||
|
||||
def _fake_run(func, *args, **kwargs):
|
||||
if func is onboarding.get_bot_info:
|
||||
return User(id=1, username="my_bot")
|
||||
if func is onboarding.wait_for_chat:
|
||||
return onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
)
|
||||
if func is onboarding._send_confirmation:
|
||||
return True
|
||||
raise AssertionError(f"unexpected anyio.run target: {func}")
|
||||
|
||||
monkeypatch.setattr(onboarding.anyio, "run", _fake_run)
|
||||
|
||||
assert onboarding.interactive_setup(force=False) is False
|
||||
assert anyio.run(partial(onboarding.interactive_setup, force=False)) is False
|
||||
assert not config_path.exists()
|
||||
|
||||
|
||||
@@ -204,31 +230,29 @@ def test_interactive_setup_recovers_from_malformed_toml(monkeypatch, tmp_path) -
|
||||
monkeypatch.setattr(onboarding, "list_backends", lambda: [backend])
|
||||
monkeypatch.setattr(onboarding.shutil, "which", lambda _cmd: "/usr/bin/codex")
|
||||
|
||||
monkeypatch.setattr(onboarding, "_confirm", _queue_values([True, True, True]))
|
||||
monkeypatch.setattr(onboarding, "confirm_prompt", queue_values([True, True, True]))
|
||||
monkeypatch.setattr(
|
||||
onboarding.questionary, "password", _queue(["123456789:ABCdef"])
|
||||
onboarding.questionary, "password", queue_answers(["123456789:ABCdef"])
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
onboarding.questionary,
|
||||
"select",
|
||||
queue_answers(["assistant", "codex"]),
|
||||
)
|
||||
patch_live_services(
|
||||
monkeypatch,
|
||||
bot=User(id=1, username="my_bot"),
|
||||
chat=onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(onboarding.questionary, "select", _queue(["codex"]))
|
||||
|
||||
def _fake_run(func, *args, **kwargs):
|
||||
if func is onboarding.get_bot_info:
|
||||
return User(id=1, username="my_bot")
|
||||
if func is onboarding.wait_for_chat:
|
||||
return onboarding.ChatInfo(
|
||||
chat_id=123,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
)
|
||||
if func is onboarding._send_confirmation:
|
||||
return True
|
||||
raise AssertionError(f"unexpected anyio.run target: {func}")
|
||||
|
||||
monkeypatch.setattr(onboarding.anyio, "run", _fake_run)
|
||||
|
||||
assert onboarding.interactive_setup(force=True) is True
|
||||
assert anyio.run(partial(onboarding.interactive_setup, force=True)) is True
|
||||
backup = config_path.with_suffix(".toml.bak")
|
||||
assert backup.exists()
|
||||
assert backup.read_text(encoding="utf-8") == bad_toml
|
||||
@@ -238,50 +262,44 @@ def test_interactive_setup_recovers_from_malformed_toml(monkeypatch, tmp_path) -
|
||||
|
||||
|
||||
def test_capture_chat_id_with_token(monkeypatch) -> None:
|
||||
def _fake_run(func, *args, **kwargs):
|
||||
if func is onboarding.get_bot_info:
|
||||
return User(id=1, username="my_bot")
|
||||
if func is onboarding.wait_for_chat:
|
||||
return onboarding.ChatInfo(
|
||||
chat_id=456,
|
||||
username=None,
|
||||
title="takopi",
|
||||
first_name=None,
|
||||
last_name=None,
|
||||
chat_type="supergroup",
|
||||
)
|
||||
raise AssertionError(f"unexpected anyio.run target: {func}")
|
||||
patch_live_services(
|
||||
monkeypatch,
|
||||
bot=User(id=1, username="my_bot"),
|
||||
chat=onboarding.ChatInfo(
|
||||
chat_id=456,
|
||||
username=None,
|
||||
title="takopi",
|
||||
first_name=None,
|
||||
last_name=None,
|
||||
chat_type="supergroup",
|
||||
),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(onboarding.anyio, "run", _fake_run)
|
||||
|
||||
chat = onboarding.capture_chat_id(token="123456789:ABCdef")
|
||||
chat = anyio.run(partial(onboarding.capture_chat_id, token="123456789:ABCdef"))
|
||||
|
||||
assert chat is not None
|
||||
assert chat.chat_id == 456
|
||||
|
||||
|
||||
def test_capture_chat_id_prompts_for_token(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
onboarding,
|
||||
"_prompt_token",
|
||||
lambda _console: ("token", User(id=1, username="bot")),
|
||||
async def _prompt_token(_ui, _svc):
|
||||
return ("token", User(id=1, username="bot"))
|
||||
|
||||
monkeypatch.setattr(onboarding, "prompt_token", _prompt_token)
|
||||
patch_live_services(
|
||||
monkeypatch,
|
||||
bot=User(id=1, username="bot"),
|
||||
chat=onboarding.ChatInfo(
|
||||
chat_id=789,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
),
|
||||
)
|
||||
|
||||
def _fake_run(func, *args, **kwargs):
|
||||
if func is onboarding.wait_for_chat:
|
||||
return onboarding.ChatInfo(
|
||||
chat_id=789,
|
||||
username="alice",
|
||||
title=None,
|
||||
first_name="Alice",
|
||||
last_name=None,
|
||||
chat_type="private",
|
||||
)
|
||||
raise AssertionError(f"unexpected anyio.run target: {func}")
|
||||
|
||||
monkeypatch.setattr(onboarding.anyio, "run", _fake_run)
|
||||
|
||||
chat = onboarding.capture_chat_id()
|
||||
chat = anyio.run(onboarding.capture_chat_id)
|
||||
|
||||
assert chat is not None
|
||||
assert chat.chat_id == 789
|
||||
|
||||
@@ -5,6 +5,7 @@ from typer.testing import CliRunner
|
||||
|
||||
from takopi import cli
|
||||
from takopi.config import ConfigError, read_config
|
||||
from takopi.ids import RESERVED_CHAT_COMMANDS
|
||||
from takopi.settings import TakopiSettings
|
||||
|
||||
|
||||
@@ -19,7 +20,7 @@ def test_parse_projects_rejects_engine_alias() -> None:
|
||||
settings.to_projects_config(
|
||||
config_path=Path("takopi.toml"),
|
||||
engine_ids=["codex"],
|
||||
reserved=("cancel",),
|
||||
reserved=RESERVED_CHAT_COMMANDS,
|
||||
)
|
||||
|
||||
|
||||
@@ -30,7 +31,7 @@ def test_parse_projects_default_project_must_exist() -> None:
|
||||
settings.to_projects_config(
|
||||
config_path=Path("takopi.toml"),
|
||||
engine_ids=["codex"],
|
||||
reserved=("cancel",),
|
||||
reserved=RESERVED_CHAT_COMMANDS,
|
||||
)
|
||||
|
||||
|
||||
@@ -94,7 +95,7 @@ def test_projects_default_engine_unknown() -> None:
|
||||
settings.to_projects_config(
|
||||
config_path=Path("takopi.toml"),
|
||||
engine_ids=["codex"],
|
||||
reserved=("cancel",),
|
||||
reserved=RESERVED_CHAT_COMMANDS,
|
||||
)
|
||||
|
||||
|
||||
@@ -108,7 +109,7 @@ def test_projects_chat_id_cannot_match_transport_chat_id() -> None:
|
||||
settings.to_projects_config(
|
||||
config_path=Path("takopi.toml"),
|
||||
engine_ids=["codex"],
|
||||
reserved=("cancel",),
|
||||
reserved=RESERVED_CHAT_COMMANDS,
|
||||
)
|
||||
|
||||
|
||||
@@ -125,7 +126,7 @@ def test_projects_chat_id_must_be_unique() -> None:
|
||||
settings.to_projects_config(
|
||||
config_path=Path("takopi.toml"),
|
||||
engine_ids=["codex"],
|
||||
reserved=("cancel",),
|
||||
reserved=RESERVED_CHAT_COMMANDS,
|
||||
)
|
||||
|
||||
|
||||
@@ -137,6 +138,6 @@ def test_projects_relative_path_resolves(tmp_path: Path) -> None:
|
||||
projects = settings.to_projects_config(
|
||||
config_path=config_path,
|
||||
engine_ids=["codex"],
|
||||
reserved=("cancel",),
|
||||
reserved=RESERVED_CHAT_COMMANDS,
|
||||
)
|
||||
assert projects.projects["z80"].path == config_path.parent / "repo"
|
||||
|
||||
@@ -41,7 +41,12 @@ def test_build_startup_message_includes_missing_engines(tmp_path: Path) -> None:
|
||||
)
|
||||
|
||||
message = telegram_backend._build_startup_message(
|
||||
runtime, startup_pwd=str(tmp_path)
|
||||
runtime,
|
||||
startup_pwd=str(tmp_path),
|
||||
chat_id=123,
|
||||
session_mode="stateless",
|
||||
show_resume_line=True,
|
||||
topics=TelegramTopicsSettings(),
|
||||
)
|
||||
|
||||
assert "takopi is ready" in message
|
||||
@@ -79,7 +84,12 @@ def test_build_startup_message_surfaces_unavailable_engine_reasons(
|
||||
)
|
||||
|
||||
message = telegram_backend._build_startup_message(
|
||||
runtime, startup_pwd=str(tmp_path)
|
||||
runtime,
|
||||
startup_pwd=str(tmp_path),
|
||||
chat_id=123,
|
||||
session_mode="stateless",
|
||||
show_resume_line=True,
|
||||
topics=TelegramTopicsSettings(),
|
||||
)
|
||||
|
||||
assert "agents: `codex" in message
|
||||
|
||||
@@ -383,6 +383,8 @@ def test_build_bot_commands_includes_cancel_and_engine() -> None:
|
||||
|
||||
assert {"command": "cancel", "description": "cancel run"} in commands
|
||||
assert {"command": "file", "description": "upload or fetch files"} in commands
|
||||
assert {"command": "new", "description": "start a new thread"} in commands
|
||||
assert {"command": "agent", "description": "set default agent"} in commands
|
||||
assert any(cmd["command"] == "codex" for cmd in commands)
|
||||
|
||||
|
||||
@@ -414,6 +416,21 @@ def test_build_bot_commands_includes_projects() -> None:
|
||||
assert not any(cmd["command"] == "bad-name" for cmd in commands)
|
||||
|
||||
|
||||
def test_build_bot_commands_includes_topics_when_enabled() -> None:
|
||||
runner = ScriptRunner(
|
||||
[Return(answer="ok")], engine=CODEX_ENGINE, resume_value="sid"
|
||||
)
|
||||
runtime = TransportRuntime(
|
||||
router=_make_router(runner),
|
||||
projects=_empty_projects(),
|
||||
)
|
||||
|
||||
commands = build_bot_commands(runtime, include_topics=True)
|
||||
|
||||
assert {"command": "topic", "description": "create or bind a topic"} in commands
|
||||
assert {"command": "ctx", "description": "show or update topic context"} in commands
|
||||
|
||||
|
||||
def test_build_bot_commands_includes_command_plugins(monkeypatch) -> None:
|
||||
class _Command:
|
||||
id = "pingcmd"
|
||||
|
||||
@@ -12,7 +12,7 @@ class DummyTransport:
|
||||
def check_setup(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def interactive_setup(self, *, force: bool) -> bool:
|
||||
async def interactive_setup(self, *, force: bool) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def lock_token(self, *, transport_config: object, _config_path):
|
||||
|
||||
Reference in New Issue
Block a user