surfaces/tests/adapter/matrix/test_context_commands.py
Mikhail Putilovskij 73c472ecc4 feat(matrix): implement !reset via new platform_chat_id
Instead of calling a /reset endpoint on platform-agent, !reset now
generates a new thread_id (platform_chat_id) for the room. The old
WebSocket connection is closed and the next message creates a fresh
context automatically. No platform changes required.
2026-04-19 21:20:31 +03:00

232 lines
8.1 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
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": "matrix:room-1"},
)
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"] == "matrix:room-1"
@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": "matrix:room-1"},
)
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": "matrix:!room:example.org"})
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 != "matrix:!room:example.org"
assert new_id.startswith("matrix:!room:example.org#")
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": "matrix:room-1"},
)
await platform._prototype_state.set_current_session("matrix:room-1", "session-a")
await platform._prototype_state.set_last_tokens_used("matrix:room-1", 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 "Контекст чата: matrix:room-1" 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": "matrix:room-1",
},
)
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("matrix:room-1") == "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"},
)