from __future__ import annotations from types import SimpleNamespace from unittest.mock import AsyncMock import pytest from adapter.matrix.bot import MatrixBot, build_runtime from adapter.matrix.handlers.context_commands import ( make_handle_context, make_handle_load, make_handle_reset, make_handle_save, ) from adapter.matrix.store import ( get_load_pending, set_load_pending, set_room_meta, ) from core.protocol import IncomingCommand, OutgoingMessage from core.store import InMemoryStore from sdk.interface import MessageResponse from sdk.mock import MockPlatformClient from sdk.prototype_state import PrototypeStateStore class MatrixCommandPlatform(MockPlatformClient): def __init__(self) -> None: super().__init__() self._prototype_state = PrototypeStateStore() self._agent_api = object() self.send_message = AsyncMock( return_value=MessageResponse( message_id="msg-1", response="ok", tokens_used=0, finished=True, ) ) @pytest.mark.asyncio async def test_save_command_auto_name_records_session(): platform = MatrixCommandPlatform() store = InMemoryStore() await set_room_meta( store, "!room:example.org", {"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"}, ) handler = make_handle_save( agent_api=platform._agent_api, store=store, prototype_state=platform._prototype_state, ) event = IncomingCommand( user_id="u1", platform="matrix", chat_id="!room:example.org", command="save", args=[], ) result = await handler(event, None, platform, None, None) assert len(result) == 1 assert isinstance(result[0], OutgoingMessage) assert "Запрос на сохранение отправлен агенту" in result[0].text sessions = await platform._prototype_state.list_saved_sessions("u1") assert len(sessions) == 1 assert sessions[0]["name"].startswith("context-") assert sessions[0]["source_context_id"] == "41" @pytest.mark.asyncio async def test_save_command_with_name_uses_given_name(): platform = MatrixCommandPlatform() store = InMemoryStore() await set_room_meta( store, "!room:example.org", {"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"}, ) handler = make_handle_save( agent_api=platform._agent_api, store=store, prototype_state=platform._prototype_state, ) event = IncomingCommand( user_id="u1", platform="matrix", chat_id="!room:example.org", command="save", args=["my-session"], ) await handler(event, None, platform, None, None) sessions = await platform._prototype_state.list_saved_sessions("u1") assert [session["name"] for session in sessions] == ["my-session"] @pytest.mark.asyncio async def test_load_command_shows_numbered_list_and_sets_pending(): platform = MatrixCommandPlatform() runtime = build_runtime(platform=platform) await runtime.chat_mgr.get_or_create( user_id="u1", chat_id="C1", platform="matrix", surface_ref="!room:example.org", name="Chat 1", ) await platform._prototype_state.add_saved_session("u1", "session-a") await platform._prototype_state.add_saved_session("u1", "session-b") handler = make_handle_load(store=runtime.store, prototype_state=platform._prototype_state) event = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="load", args=[]) result = await handler( event, runtime.auth_mgr, platform, runtime.chat_mgr, runtime.settings_mgr, ) assert "1. session-a" in result[0].text assert "2. session-b" in result[0].text pending = await get_load_pending(runtime.store, "u1", "!room:example.org") assert pending is not None assert [session["name"] for session in pending["saves"]] == ["session-a", "session-b"] @pytest.mark.asyncio async def test_load_command_without_saved_sessions_reports_empty(): platform = MatrixCommandPlatform() store = InMemoryStore() handler = make_handle_load(store=store, prototype_state=platform._prototype_state) event = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="load", args=[]) result = await handler(event, None, platform, None, None) assert "Нет сохранённых сессий" in result[0].text @pytest.mark.asyncio async def test_reset_command_assigns_new_platform_chat_id(): from adapter.matrix.store import get_platform_chat_id, set_room_meta from sdk.prototype_state import PrototypeStateStore prototype_state = PrototypeStateStore() platform = MatrixCommandPlatform() runtime = build_runtime(platform=platform) store = runtime.store await set_room_meta(store, "!room:example.org", {"platform_chat_id": "7"}) handler = make_handle_reset(store=store, prototype_state=prototype_state) event = IncomingCommand( user_id="u1", platform="matrix", chat_id="!room:example.org", command="reset", args=[], ) result = await handler( event, runtime.auth_mgr, platform, runtime.chat_mgr, runtime.settings_mgr, ) new_id = await get_platform_chat_id(store, "!room:example.org") assert new_id != "7" assert new_id == "1" assert "сброшен" in result[0].text.lower() @pytest.mark.asyncio async def test_context_command_shows_current_snapshot(): platform = MatrixCommandPlatform() runtime = build_runtime(platform=platform) await runtime.chat_mgr.get_or_create( user_id="u1", chat_id="C1", platform="matrix", surface_ref="!room:example.org", name="Chat 1", ) await set_room_meta( runtime.store, "!room:example.org", {"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"}, ) await platform._prototype_state.set_current_session("41", "session-a") await platform._prototype_state.set_last_tokens_used("41", 99) await platform._prototype_state.add_saved_session("u1", "session-a") handler = make_handle_context(store=runtime.store, prototype_state=platform._prototype_state) event = IncomingCommand( user_id="u1", platform="matrix", chat_id="C1", command="context", args=[], ) result = await handler( event, runtime.auth_mgr, platform, runtime.chat_mgr, runtime.settings_mgr, ) assert "Контекст чата: 41" in result[0].text assert "Сессия: session-a" in result[0].text assert "Токены (последний ответ): 99" in result[0].text assert "session-a" in result[0].text @pytest.mark.asyncio async def test_bot_intercepts_numeric_load_selection(): platform = MatrixCommandPlatform() runtime = build_runtime(platform=platform) await set_room_meta( runtime.store, "!room:example.org", { "chat_id": "C1", "matrix_user_id": "@alice:example.org", "platform_chat_id": "41", }, ) client = SimpleNamespace( user_id="@bot:example.org", room_send=AsyncMock(), ) bot = MatrixBot(client, runtime) await set_load_pending( runtime.store, "@alice:example.org", "!room:example.org", {"saves": [{"name": "session-a", "created_at": "2026-04-17T00:00:00+00:00"}]}, ) room = SimpleNamespace(room_id="!room:example.org") event = SimpleNamespace(sender="@alice:example.org", body="1") await bot.on_room_message(room, event) platform.send_message.assert_awaited_once() assert await platform._prototype_state.get_current_session("41") == "session-a" assert await platform._prototype_state.get_current_session("C1") == "session-a" client.room_send.assert_awaited_once_with( "!room:example.org", "m.room.message", {"msgtype": "m.text", "body": "Запрос на загрузку отправлен агенту: session-a"}, )