from __future__ import annotations from pathlib import Path from types import SimpleNamespace import pytest from nanobot.bus.events import InboundMessage from nanobot.command.builtin import build_help_text, register_builtin_commands from nanobot.command.router import CommandContext, CommandRouter from nanobot.session.keys import UNIFIED_SESSION_KEY from nanobot.triggers.local_store import LocalTriggerStore @pytest.mark.asyncio async def test_trigger_command_creates_session_bound_local_trigger(tmp_path: Path) -> None: router = CommandRouter() register_builtin_commands(router) store = LocalTriggerStore(tmp_path) loop = SimpleNamespace(workspace=tmp_path, local_trigger_store=store) msg = InboundMessage( channel="websocket", sender_id="user", chat_id="chat-1", content="/trigger@nanobot_bot PR review", metadata={"webui": True}, ) ctx = CommandContext( msg=msg, session=None, key="websocket:chat-1", raw="/trigger@nanobot_bot PR review", loop=loop, ) assert router.is_dispatchable_command("/trigger@nanobot_bot PR review") is True response = await router.dispatch(ctx) assert response is not None assert "Trigger created: PR review" in response.content trigger = store.list_for_session("websocket:chat-1")[0] assert trigger.name == "PR review" assert trigger.channel == "websocket" assert trigger.chat_id == "chat-1" assert trigger.session_key == "websocket:chat-1" assert f"nanobot trigger {trigger.id} \"message\"" in response.content @pytest.mark.asyncio async def test_trigger_command_binds_inbound_session_when_unified_session_is_active( tmp_path: Path, ) -> None: router = CommandRouter() register_builtin_commands(router) store = LocalTriggerStore(tmp_path) loop = SimpleNamespace(workspace=tmp_path, local_trigger_store=store) msg = InboundMessage( channel="websocket", sender_id="user", chat_id="chat-1", content="/trigger PR review", session_key_override="websocket:chat-1:thread-a", ) ctx = CommandContext( msg=msg, session=None, key=UNIFIED_SESSION_KEY, raw="/trigger PR review", loop=loop, ) response = await router.dispatch(ctx) assert response is not None trigger = store.list_for_session("websocket:chat-1:thread-a")[0] assert trigger.session_key == "websocket:chat-1:thread-a" assert store.list_for_session(UNIFIED_SESSION_KEY) == [] @pytest.mark.asyncio async def test_trigger_command_without_name_returns_usage_only(tmp_path: Path) -> None: router = CommandRouter() register_builtin_commands(router) store = LocalTriggerStore(tmp_path) loop = SimpleNamespace(workspace=tmp_path, local_trigger_store=store) msg = InboundMessage( channel="websocket", sender_id="user", chat_id="chat-1", content="/trigger@nanobot_bot", metadata={"webui": True}, ) ctx = CommandContext( msg=msg, session=None, key="websocket:chat-1", raw="/trigger@nanobot_bot", loop=loop, ) response = await router.dispatch(ctx) assert response is not None assert "Usage: /trigger " in response.content assert store.list_for_session("websocket:chat-1") == [] def test_trigger_command_is_in_help_text() -> None: assert "/trigger " in build_help_text()