From ae37476ddf8f9ac6bdcef52a42b0f57f27cc8b89 Mon Sep 17 00:00:00 2001 From: Mikhail Putilovskij Date: Tue, 28 Apr 2026 01:13:54 +0300 Subject: [PATCH] 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 --- tests/adapter/matrix/test_context_commands.py | 92 ++++++++++++++++++- tests/adapter/matrix/test_routed_platform.py | 80 ++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) diff --git a/tests/adapter/matrix/test_context_commands.py b/tests/adapter/matrix/test_context_commands.py index a289772..9264a06 100644 --- a/tests/adapter/matrix/test_context_commands.py +++ b/tests/adapter/matrix/test_context_commands.py @@ -1,11 +1,12 @@ from __future__ import annotations from types import SimpleNamespace -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, Mock import pytest from adapter.matrix.bot import MatrixBot, build_runtime +from adapter.matrix.handlers import register_matrix_handlers from adapter.matrix.handlers.context_commands import ( make_handle_context, make_handle_load, @@ -29,6 +30,7 @@ class MatrixCommandPlatform(MockPlatformClient): super().__init__() self._prototype_state = PrototypeStateStore() self._agent_api = object() + self.disconnect_chat = AsyncMock() self.send_message = AsyncMock( return_value=MessageResponse( 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 async def test_save_command_auto_name_records_session(): platform = MatrixCommandPlatform() @@ -179,6 +187,88 @@ async def test_reset_command_assigns_new_platform_chat_id(): 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 async def test_context_command_shows_current_snapshot(): platform = MatrixCommandPlatform() diff --git a/tests/adapter/matrix/test_routed_platform.py b/tests/adapter/matrix/test_routed_platform.py index 1aa3400..c3efca5 100644 --- a/tests/adapter/matrix/test_routed_platform.py +++ b/tests/adapter/matrix/test_routed_platform.py @@ -14,6 +14,7 @@ from core.chat import ChatManager from core.store import InMemoryStore from sdk.interface import MessageChunk, MessageResponse, User, UserSettings from sdk.mock import MockPlatformClient +from sdk.interface import PlatformError class FakeDelegate: @@ -99,6 +100,12 @@ class FakeDelegate: 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 async def test_send_message_routes_by_room_agent_and_platform_chat_id(): 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 async def test_user_and_settings_delegate_to_default_client(): store = InMemoryStore()