test(05-02): add failing regressions for clear routing

- cover room-local clear rotation and upstream disconnect behavior
- assert strict routed-platform failures on incomplete room bindings
This commit is contained in:
Mikhail Putilovskij 2026-04-28 01:13:54 +03:00
parent 8a80d004fd
commit ae37476ddf
2 changed files with 171 additions and 1 deletions

View file

@ -1,11 +1,12 @@
from __future__ import annotations from __future__ import annotations
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import AsyncMock from unittest.mock import AsyncMock, Mock
import pytest import pytest
from adapter.matrix.bot import MatrixBot, build_runtime from adapter.matrix.bot import MatrixBot, build_runtime
from adapter.matrix.handlers import register_matrix_handlers
from adapter.matrix.handlers.context_commands import ( from adapter.matrix.handlers.context_commands import (
make_handle_context, make_handle_context,
make_handle_load, make_handle_load,
@ -29,6 +30,7 @@ class MatrixCommandPlatform(MockPlatformClient):
super().__init__() super().__init__()
self._prototype_state = PrototypeStateStore() self._prototype_state = PrototypeStateStore()
self._agent_api = object() self._agent_api = object()
self.disconnect_chat = AsyncMock()
self.send_message = AsyncMock( self.send_message = AsyncMock(
return_value=MessageResponse( return_value=MessageResponse(
message_id="msg-1", message_id="msg-1",
@ -39,6 +41,12 @@ class MatrixCommandPlatform(MockPlatformClient):
) )
@pytest.fixture(autouse=True)
def clear_matrix_registry_env(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("MATRIX_AGENT_REGISTRY_PATH", raising=False)
monkeypatch.delenv("MATRIX_PLATFORM_BACKEND", raising=False)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_save_command_auto_name_records_session(): async def test_save_command_auto_name_records_session():
platform = MatrixCommandPlatform() platform = MatrixCommandPlatform()
@ -179,6 +187,88 @@ async def test_reset_command_assigns_new_platform_chat_id():
assert "сброшен" in result[0].text.lower() assert "сброшен" in result[0].text.lower()
@pytest.mark.asyncio
async def test_clear_command_rotates_only_current_room_and_disconnects_old_upstream_chat():
from adapter.matrix.store import get_platform_chat_id
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-a:example.org",
name="Chat A",
)
await runtime.chat_mgr.get_or_create(
user_id="u1",
chat_id="C2",
platform="matrix",
surface_ref="!room-b:example.org",
name="Chat B",
)
await set_room_meta(
runtime.store,
"!room-a:example.org",
{"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"},
)
await set_room_meta(
runtime.store,
"!room-b:example.org",
{"chat_id": "C2", "matrix_user_id": "u1", "platform_chat_id": "99"},
)
handler = make_handle_reset(store=runtime.store, prototype_state=platform._prototype_state)
event = IncomingCommand(
user_id="u1",
platform="matrix",
chat_id="C1",
command="clear",
args=[],
)
result = await handler(
event,
runtime.auth_mgr,
platform,
runtime.chat_mgr,
runtime.settings_mgr,
)
room_a_chat_id = await get_platform_chat_id(runtime.store, "!room-a:example.org")
room_b_chat_id = await get_platform_chat_id(runtime.store, "!room-b:example.org")
assert room_a_chat_id == "1"
assert room_a_chat_id != "41"
assert room_b_chat_id == "99"
platform.disconnect_chat.assert_awaited_once_with("41")
assert "сброшен" in result[0].text.lower()
def test_register_matrix_handlers_exposes_clear_and_optional_reset_alias():
dispatcher = SimpleNamespace(register=Mock())
register_matrix_handlers(
dispatcher,
client=object(),
store=object(),
registry=None,
prototype_state=PrototypeStateStore(),
)
clear_calls = [
call
for call in dispatcher.register.call_args_list
if call.args[:2] == (IncomingCommand, "clear")
]
reset_calls = [
call
for call in dispatcher.register.call_args_list
if call.args[:2] == (IncomingCommand, "reset")
]
assert clear_calls
assert len(reset_calls) <= 1
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_context_command_shows_current_snapshot(): async def test_context_command_shows_current_snapshot():
platform = MatrixCommandPlatform() platform = MatrixCommandPlatform()

View file

@ -14,6 +14,7 @@ from core.chat import ChatManager
from core.store import InMemoryStore from core.store import InMemoryStore
from sdk.interface import MessageChunk, MessageResponse, User, UserSettings from sdk.interface import MessageChunk, MessageResponse, User, UserSettings
from sdk.mock import MockPlatformClient from sdk.mock import MockPlatformClient
from sdk.interface import PlatformError
class FakeDelegate: class FakeDelegate:
@ -99,6 +100,12 @@ class FakeDelegate:
self.update_calls.append((user_id, action)) self.update_calls.append((user_id, action))
@pytest.fixture(autouse=True)
def clear_matrix_registry_env(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("MATRIX_AGENT_REGISTRY_PATH", raising=False)
monkeypatch.delenv("MATRIX_PLATFORM_BACKEND", raising=False)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_send_message_routes_by_room_agent_and_platform_chat_id(): async def test_send_message_routes_by_room_agent_and_platform_chat_id():
store = InMemoryStore() store = InMemoryStore()
@ -159,6 +166,79 @@ async def test_stream_message_routes_by_room_agent_and_platform_chat_id():
] ]
@pytest.mark.asyncio
async def test_send_message_fails_fast_when_platform_chat_id_is_missing():
store = InMemoryStore()
chat_mgr = ChatManager(None, store)
await chat_mgr.get_or_create("u1", "C1", "matrix", "!room:example.org")
await set_room_meta(
store,
"!room:example.org",
{"agent_id": "agent-2"},
)
platform = RoutedPlatformClient(
chat_mgr=chat_mgr,
store=store,
delegates={"agent-2": FakeDelegate(name="agent-2")},
)
with pytest.raises(PlatformError, match="routing is incomplete") as exc_info:
await platform.send_message("u1", "C1", "hello")
assert exc_info.value.code == "MATRIX_ROUTE_INCOMPLETE"
@pytest.mark.asyncio
async def test_stream_message_fails_fast_when_agent_id_is_missing():
store = InMemoryStore()
chat_mgr = ChatManager(None, store)
await chat_mgr.get_or_create("u1", "C1", "matrix", "!room:example.org")
await set_room_meta(
store,
"!room:example.org",
{"platform_chat_id": "41"},
)
platform = RoutedPlatformClient(
chat_mgr=chat_mgr,
store=store,
delegates={"agent-2": FakeDelegate(name="agent-2")},
)
with pytest.raises(PlatformError, match="routing is incomplete") as exc_info:
await anext(platform.stream_message("u1", "C1", "hello"))
assert exc_info.value.code == "MATRIX_ROUTE_INCOMPLETE"
@pytest.mark.asyncio
async def test_routing_uses_repaired_room_metadata_without_runtime_backfill():
store = InMemoryStore()
chat_mgr = ChatManager(None, store)
await chat_mgr.get_or_create("u1", "C1", "matrix", "!room:example.org")
await set_room_meta(
store,
"!room:example.org",
{"platform_chat_id": "restored-41", "agent_id": "agent-2"},
)
delegate = FakeDelegate(name="agent-2")
platform = RoutedPlatformClient(
chat_mgr=chat_mgr,
store=store,
delegates={"agent-2": delegate},
)
await platform.send_message("u1", "C1", "hello")
assert delegate.send_calls == [
{
"user_id": "u1",
"chat_id": "restored-41",
"text": "hello",
"attachments": None,
}
]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_user_and_settings_delegate_to_default_client(): async def test_user_and_settings_delegate_to_default_client():
store = InMemoryStore() store = InMemoryStore()