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:
parent
8a80d004fd
commit
ae37476ddf
2 changed files with 171 additions and 1 deletions
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue